From 585b6ccd3e3dbb4f9941824e566829aa882884b7 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 11 Mar 2026 19:05:44 -0700 Subject: [PATCH 001/203] feat(search): try to improve performance --- apps/server/spec/search_profiling.spec.ts | 284 ++++++++++ .../src/services/search/services/search.ts | 19 +- .../search/services/search_profiling.spec.ts | 526 ++++++++++++++++++ 3 files changed, 819 insertions(+), 10 deletions(-) create mode 100644 apps/server/spec/search_profiling.spec.ts create mode 100644 apps/server/src/services/search/services/search_profiling.spec.ts diff --git a/apps/server/spec/search_profiling.spec.ts b/apps/server/spec/search_profiling.spec.ts new file mode 100644 index 0000000000..9f5f848034 --- /dev/null +++ b/apps/server/spec/search_profiling.spec.ts @@ -0,0 +1,284 @@ +/** + * Integration-level search profiling test. + * + * Uses the real SQLite database (spec/db/document.db loaded in-memory), + * real sql module, real becca cache, and the full app stack. + * + * Seeds a large number of notes via direct SQL (much faster than ETAPI) + * to create a realistic dataset for profiling. + */ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import config from "../src/services/config.js"; + +let app: Application; + +function timed(fn: () => T): [T, number] { + const start = performance.now(); + const result = fn(); + return [result, performance.now() - start]; +} + +function randomId(len = 12): string { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let id = ""; + for (let i = 0; i < len; i++) id += chars[Math.floor(Math.random() * chars.length)]; + return id; +} + +function randomWord(len = 8): string { + const chars = "abcdefghijklmnopqrstuvwxyz"; + let w = ""; + for (let i = 0; i < len; i++) w += chars[Math.floor(Math.random() * chars.length)]; + return w; +} + +function generateContent(wordCount: number, keyword?: string): string { + const paragraphs: string[] = []; + let remaining = wordCount; + let injected = false; + while (remaining > 0) { + const n = Math.min(remaining, 30 + Math.floor(Math.random() * 30)); + const words: string[] = []; + for (let i = 0; i < n; i++) words.push(randomWord(3 + Math.floor(Math.random() * 10))); + if (keyword && !injected && remaining < wordCount / 2) { + words[Math.floor(words.length / 2)] = keyword; + injected = true; + } + paragraphs.push(`

${words.join(" ")}

`); + remaining -= n; + } + return paragraphs.join("\n"); +} + +describe("Search profiling (integration)", () => { + beforeAll(async () => { + config.General.noAuthentication = true; + const buildApp = (await import("../src/app.js")).default; + app = await buildApp(); + }); + + it("seed and profile with realistic data", async () => { + const sql = (await import("../src/services/sql.js")).default; + const becca = (await import("../src/becca/becca.js")).default; + const beccaLoader = (await import("../src/becca/becca_loader.js")).default; + const cls = (await import("../src/services/cls.js")).default; + const searchService = (await import("../src/services/search/services/search.js")).default; + const SearchContext = (await import("../src/services/search/search_context.js")).default; + + await new Promise((resolve) => { + cls.init(() => { + const initialNoteCount = Object.keys(becca.notes).length; + console.log(`\n Initial becca notes: ${initialNoteCount}`); + + const configs = [ + { notes: 2000, words: 500, label: "2K notes × 500 words (~4KB)" }, + { notes: 2000, words: 2000, label: "2K notes × 2000 words (~15KB)" }, + { notes: 5000, words: 500, label: "5K notes × 500 words (~4KB)" }, + { notes: 5000, words: 2000, label: "5K notes × 2000 words (~15KB)" }, + { notes: 10000, words: 1000, label: "10K notes × 1000 words (~8KB)" }, + ]; + + for (const cfg of configs) { + // Reset DB: delete all seeded notes from prior iteration + sql.execute(`DELETE FROM blobs WHERE blobId LIKE 'seed%'`); + sql.execute(`DELETE FROM notes WHERE noteId LIKE 'seed%'`); + sql.execute(`DELETE FROM branches WHERE branchId LIKE 'seed%'`); + + const TOTAL_NOTES = cfg.notes; + const MATCH_FRACTION = 0.15; + const CONTENT_WORDS = cfg.words; + const matchCount = Math.floor(TOTAL_NOTES * MATCH_FRACTION); + + const now = new Date().toISOString().replace("T", " ").replace("Z", "+0000"); + + console.log(`\n ──── ${cfg.label} ────`); + console.log(` Seeding ${TOTAL_NOTES} notes (${matchCount} with keyword)...`); + + const [, seedMs] = timed(() => { + sql.transactional(() => { + for (let i = 0; i < TOTAL_NOTES; i++) { + const isMatch = i < matchCount; + const noteId = `seed${randomId(8)}`; + const branchId = `seed${randomId(8)}`; + const blobId = `seed${randomId(16)}`; + const title = isMatch + ? `Performance Doc ${i} ${randomWord(6)}` + : `General Note ${i} ${randomWord(6)} ${randomWord(5)}`; + const content = generateContent( + CONTENT_WORDS, + isMatch ? "performance" : undefined + ); + + sql.execute( + `INSERT INTO blobs (blobId, content, dateModified, utcDateModified) + VALUES (?, ?, ?, ?)`, + [blobId, content, now, now] + ); + + sql.execute( + `INSERT INTO notes (noteId, title, type, mime, blobId, isProtected, isDeleted, + dateCreated, dateModified, utcDateCreated, utcDateModified) + VALUES (?, ?, 'text', 'text/html', ?, 0, 0, ?, ?, ?, ?)`, + [noteId, title, blobId, now, now, now, now] + ); + + sql.execute( + `INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, isDeleted, isExpanded, + utcDateModified) + VALUES (?, ?, 'root', ?, 0, 0, ?)`, + [branchId, noteId, i * 10, now] + ); + } + }); + }); + console.log(` SQL seeding: ${seedMs.toFixed(0)}ms`); + + // Reload becca to pick up new notes + const [, reloadMs] = timed(() => { + beccaLoader.load(); + }); + console.log(` Becca reload: ${reloadMs.toFixed(0)}ms`); + console.log(` Becca notes after seed: ${Object.keys(becca.notes).length}`); + + // Verify content is accessible + const sampleNote = Object.values(becca.notes).find(n => n.title.startsWith("Performance Doc")); + if (sampleNote) { + const content = sampleNote.getContent(); + console.log(` Sample content length: ${typeof content === 'string' ? content.length : 0} chars`); + } + + // ========================================== + // PROFILING + // ========================================== + + console.log(`\n --- PROFILING (${cfg.label}) ---\n`); + + // --- 1. Fast search (NoteFlatTextExp only) --- + searchService.findResultsWithQuery("performance", new SearchContext({ fastSearch: true })); + + const fastTimes: number[] = []; + let fastResultCount = 0; + for (let i = 0; i < 5; i++) { + const [r, ms] = timed(() => + searchService.findResultsWithQuery("performance", + new SearchContext({ fastSearch: true }) + ) + ); + fastTimes.push(ms); + fastResultCount = r.length; + } + const fastAvg = fastTimes.reduce((a, b) => a + b, 0) / fastTimes.length; + console.log(` Fast search (flat text only): avg ${fastAvg.toFixed(1)}ms (${fastResultCount} results)`); + + // --- 2. Full search (flat text + content fulltext via SQL) --- + const fullTimes: number[] = []; + let fullResultCount = 0; + for (let i = 0; i < 3; i++) { + const [r, ms] = timed(() => + searchService.findResultsWithQuery("performance", + new SearchContext({ fastSearch: false }) + ) + ); + fullTimes.push(ms); + fullResultCount = r.length; + } + const fullAvg = fullTimes.reduce((a, b) => a + b, 0) / fullTimes.length; + console.log(` Full search (flat + SQL content): avg ${fullAvg.toFixed(1)}ms (${fullResultCount} results)`); + + // --- 3. Content snippet extraction --- + const fastResults = searchService.findResultsWithQuery("performance", + new SearchContext({ fastSearch: true })); + const trimmed = fastResults.slice(0, 200); + const tokens = ["performance"]; + + const snippetTimes: number[] = []; + for (let i = 0; i < 3; i++) { + const [, ms] = timed(() => { + for (const r of trimmed) { + r.contentSnippet = searchService.extractContentSnippet(r.noteId, tokens); + } + }); + snippetTimes.push(ms); + } + const snippetAvg = snippetTimes.reduce((a, b) => a + b, 0) / snippetTimes.length; + console.log(` Content snippet (${trimmed.length} results): avg ${snippetAvg.toFixed(1)}ms (${(snippetAvg / trimmed.length).toFixed(3)}ms/note)`); + + // --- 4. Raw getContent() cost --- + const contentTimes: number[] = []; + const textNotes = trimmed + .map(r => becca.notes[r.noteId]) + .filter(n => n && ["text", "code"].includes(n.type)); + + for (let i = 0; i < 5; i++) { + const [, ms] = timed(() => { + for (const n of textNotes) n.getContent(); + }); + contentTimes.push(ms); + } + const contentAvg = contentTimes.reduce((a, b) => a + b, 0) / contentTimes.length; + console.log(` getContent() × ${textNotes.length} notes: avg ${contentAvg.toFixed(1)}ms (${(contentAvg / textNotes.length).toFixed(3)}ms/note)`); + + // --- 5. striptags + normalize cost (isolated) --- + const striptags = require("striptags"); + const normalizeString = require("normalize-strings"); + const contents = textNotes.map(n => n.getContent() as string).filter(Boolean); + + const [, stripMs] = timed(() => { + for (const c of contents) { + striptags(c); + } + }); + console.log(` striptags × ${contents.length} notes: ${stripMs.toFixed(1)}ms (${(stripMs / contents.length).toFixed(3)}ms/note)`); + + const stripped = contents.map(c => striptags(c)); + const [, normMs] = timed(() => { + for (const s of stripped) { + normalizeString(s.toLowerCase()); + } + }); + console.log(` normalizeString × ${stripped.length} notes: ${normMs.toFixed(1)}ms (${(normMs / stripped.length).toFixed(3)}ms/note)`); + + // --- 6. Full autocomplete --- + const autoTimes: number[] = []; + let autoResultCount = 0; + for (let i = 0; i < 3; i++) { + const [r, ms] = timed(() => + searchService.searchNotesForAutocomplete("performance", true) + ); + autoTimes.push(ms); + autoResultCount = r.length; + } + const autoAvg = autoTimes.reduce((a, b) => a + b, 0) / autoTimes.length; + console.log(`\n FULL AUTOCOMPLETE: avg ${autoAvg.toFixed(1)}ms (${autoResultCount} results)`); + + // --- 7. SQL content scan cost --- + const [scanCount, scanMs] = timed(() => { + let count = 0; + for (const row of sql.iterateRows<{ content: Buffer | string }>(` + SELECT noteId, type, mime, content, isProtected + FROM notes JOIN blobs USING (blobId) + WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') + AND isDeleted = 0 + AND LENGTH(content) < 2097152`)) { + count++; + } + return count; + }); + console.log(` SQL content scan (${scanCount} rows): ${scanMs.toFixed(1)}ms`); + + // --- Summary --- + console.log(`\n === SUMMARY (${cfg.label}, ${Object.keys(becca.notes).length} total notes) ===`); + console.log(` Fast search: ${fastAvg.toFixed(1)}ms`); + console.log(` Full search: ${fullAvg.toFixed(1)}ms`); + console.log(` Content snippets: ${snippetAvg.toFixed(1)}ms (${(snippetAvg / trimmed.length).toFixed(3)}ms/note)`); + console.log(` normalizeString: ${normMs.toFixed(1)}ms (${(normMs / stripped.length).toFixed(3)}ms/note)`); + console.log(` Full autocomplete: ${autoAvg.toFixed(1)}ms`); + console.log(` SQL scan: ${scanMs.toFixed(1)}ms (${scanCount} rows)`); + } + + resolve(); + }); + }); + }, 600_000); +}); diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index 5ca4bda4a1..4701964f5b 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -1,6 +1,5 @@ "use strict"; -import normalizeString from "normalize-strings"; import lex from "./lex.js"; import handleParens from "./handle_parens.js"; import parse from "./parse.js"; @@ -8,7 +7,7 @@ import SearchResult from "../search_result.js"; import SearchContext from "../search_context.js"; import becca from "../../../becca/becca.js"; import beccaService from "../../../becca/becca_service.js"; -import { normalize, escapeHtml, escapeRegExp } from "../../utils.js"; +import { normalize, removeDiacritic, escapeHtml, escapeRegExp } from "../../utils.js"; import log from "../../log.js"; import hoistedNoteService from "../../hoisted_note.js"; import type BNote from "../../../becca/entities/bnote.js"; @@ -482,12 +481,12 @@ function extractContentSnippet(noteId: string, searchTokens: string[], maxLength } // Try to find a snippet around the first matching token - const normalizedContent = normalizeString(content.toLowerCase()); + const normalizedContent = normalize(content); let snippetStart = 0; let matchFound = false; for (const token of searchTokens) { - const normalizedToken = normalizeString(token.toLowerCase()); + const normalizedToken = normalize(token); const matchIndex = normalizedContent.indexOf(normalizedToken); if (matchIndex !== -1) { @@ -505,8 +504,8 @@ function extractContentSnippet(noteId: string, searchTokens: string[], maxLength const lines = snippet.split('\n'); if (lines.length > 4) { // Find which lines contain the search tokens to ensure they're included - const normalizedLines = lines.map(line => normalizeString(line.toLowerCase())); - const normalizedTokens = searchTokens.map(token => normalizeString(token.toLowerCase())); + const normalizedLines = lines.map(line => normalize(line)); + const normalizedTokens = searchTokens.map(token => normalize(token)); // Find the first line that contains a search token let firstMatchLine = -1; @@ -582,7 +581,7 @@ function extractAttributeSnippet(noteId: string, searchTokens: string[], maxLeng // Check if any search token matches the attribute name or value const hasMatch = searchTokens.some(token => { - const normalizedToken = normalizeString(token.toLowerCase()); + const normalizedToken = normalize(token); return attrName.includes(normalizedToken) || attrValue.includes(normalizedToken); }); @@ -734,7 +733,7 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens // Highlight in note path title if (result.highlightedNotePathTitle) { const titleRegex = new RegExp(escapeRegExp(token), "gi"); - while ((match = titleRegex.exec(normalizeString(result.highlightedNotePathTitle))) !== null) { + while ((match = titleRegex.exec(removeDiacritic(result.highlightedNotePathTitle))) !== null) { result.highlightedNotePathTitle = wrapText(result.highlightedNotePathTitle, match.index, token.length, "{", "}"); // 2 characters are added, so we need to adjust the index titleRegex.lastIndex += 2; @@ -744,7 +743,7 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens // Highlight in content snippet if (result.highlightedContentSnippet) { const contentRegex = new RegExp(escapeRegExp(token), "gi"); - while ((match = contentRegex.exec(normalizeString(result.highlightedContentSnippet))) !== null) { + while ((match = contentRegex.exec(removeDiacritic(result.highlightedContentSnippet))) !== null) { result.highlightedContentSnippet = wrapText(result.highlightedContentSnippet, match.index, token.length, "{", "}"); // 2 characters are added, so we need to adjust the index contentRegex.lastIndex += 2; @@ -754,7 +753,7 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens // Highlight in attribute snippet if (result.highlightedAttributeSnippet) { const attributeRegex = new RegExp(escapeRegExp(token), "gi"); - while ((match = attributeRegex.exec(normalizeString(result.highlightedAttributeSnippet))) !== null) { + while ((match = attributeRegex.exec(removeDiacritic(result.highlightedAttributeSnippet))) !== null) { result.highlightedAttributeSnippet = wrapText(result.highlightedAttributeSnippet, match.index, token.length, "{", "}"); // 2 characters are added, so we need to adjust the index attributeRegex.lastIndex += 2; diff --git a/apps/server/src/services/search/services/search_profiling.spec.ts b/apps/server/src/services/search/services/search_profiling.spec.ts new file mode 100644 index 0000000000..96a414b257 --- /dev/null +++ b/apps/server/src/services/search/services/search_profiling.spec.ts @@ -0,0 +1,526 @@ +/** + * Search performance profiling tests. + * + * These tests measure where time is spent in the search pipeline. + * We monkeypatch note.getContent() to return synthetic HTML content + * since unit tests don't have a real SQLite database. + * + * KNOWN GAPS vs production: + * - note.getContent() is instant (monkeypatched) vs ~2ms SQL fetch + * - NoteContentFulltextExp.execute() is skipped (no sql.iterateRows) + * because fastSearch=true uses only NoteFlatTextExp + * - These tests focus on the in-memory/CPU-bound parts of the pipeline + */ +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import searchService from "./search.js"; +import BNote from "../../../becca/entities/bnote.js"; +import BBranch from "../../../becca/entities/bbranch.js"; +import SearchContext from "../search_context.js"; +import becca from "../../../becca/becca.js"; +import beccaService from "../../../becca/becca_service.js"; +import { NoteBuilder, note, id } from "../../../test/becca_mocking.js"; +import SearchResult from "../search_result.js"; +import { normalizeSearchText } from "../utils/text_utils.js"; + +// ── helpers ────────────────────────────────────────────────────────── + +function randomWord(len = 6): string { + const chars = "abcdefghijklmnopqrstuvwxyz"; + let word = ""; + for (let i = 0; i < len; i++) { + word += chars[Math.floor(Math.random() * chars.length)]; + } + return word; +} + +function generateHtmlContent(wordCount: number, includeTarget = false): string { + const paragraphs: string[] = []; + let wordsRemaining = wordCount; + + while (wordsRemaining > 0) { + const paraWords = Math.min(wordsRemaining, 20 + Math.floor(Math.random() * 40)); + const words: string[] = []; + for (let i = 0; i < paraWords; i++) { + words.push(randomWord(3 + Math.floor(Math.random() * 10))); + } + if (includeTarget && paragraphs.length === 2) { + words[Math.floor(words.length / 2)] = "target"; + } + paragraphs.push(`

${words.join(" ")}

`); + wordsRemaining -= paraWords; + } + + return `${paragraphs.join("\n")}`; +} + +function timed(fn: () => T): [T, number] { + const start = performance.now(); + const result = fn(); + return [result, performance.now() - start]; +} + +interface TimingEntry { label: string; ms: number; } + +function reportTimings(title: string, timings: TimingEntry[]) { + const total = timings.reduce((s, t) => s + t.ms, 0); + console.log(`\n=== ${title} (total: ${total.toFixed(1)}ms) ===`); + for (const { label, ms } of timings) { + const pct = total > 0 ? ((ms / total) * 100).toFixed(0) : "0"; + const bar = "#".repeat(Math.max(1, Math.round(ms / total * 40))); + console.log(` ${label.padEnd(55)} ${ms.toFixed(1).padStart(8)}ms ${pct.padStart(3)}% ${bar}`); + } +} + +// ── dataset builder ────────────────────────────────────────────────── + +const syntheticContent: Record = {}; + +function buildDataset(noteCount: number, opts: { + matchFraction?: number; + labelsPerNote?: number; + depth?: number; + contentWordCount?: number; +} = {}) { + const { + matchFraction = 0.1, + labelsPerNote = 3, + depth = 3, + contentWordCount = 200, + } = opts; + + becca.reset(); + for (const key of Object.keys(syntheticContent)) { + delete syntheticContent[key]; + } + + const rootNote = new NoteBuilder(new BNote({ noteId: "root", title: "root", type: "text" })); + new BBranch({ + branchId: "none_root", + noteId: "root", + parentNoteId: "none", + notePosition: 10 + }); + + const containers: NoteBuilder[] = []; + let parent = rootNote; + for (let d = 0; d < depth; d++) { + const container = note(`Container_${d}_${randomWord(4)}`); + parent.child(container); + containers.push(container); + parent = container; + } + + const matchCount = Math.floor(noteCount * matchFraction); + + for (let i = 0; i < noteCount; i++) { + const isMatch = i < matchCount; + const title = isMatch + ? `${randomWord(5)} target ${randomWord(5)} Document ${i}` + : `${randomWord(5)} ${randomWord(6)} ${randomWord(4)} Note ${i}`; + + const n = note(title); + + for (let l = 0; l < labelsPerNote; l++) { + const labelName = isMatch && l === 0 ? "category" : `label_${randomWord(4)}`; + const labelValue = isMatch && l === 0 ? "important target" : randomWord(8); + n.label(labelName, labelValue); + } + + syntheticContent[n.note.noteId] = generateHtmlContent(contentWordCount, isMatch); + + const containerIndex = i % containers.length; + containers[containerIndex].child(n); + } + + // Monkeypatch getContent() + for (const noteObj of Object.values(becca.notes)) { + const noteId = noteObj.noteId; + if (syntheticContent[noteId]) { + (noteObj as any).getContent = () => syntheticContent[noteId]; + } else { + (noteObj as any).getContent = () => ""; + } + } + + return { rootNote, matchCount }; +} + +// ── profiling tests ────────────────────────────────────────────────── + +describe("Search Profiling", () => { + + afterEach(() => { + becca.reset(); + }); + + /** + * Break down the autocomplete pipeline into every individual stage, + * including previously unmeasured operations like getBestNotePath, + * SearchResult construction, and getNoteTitleForPath. + */ + describe("Granular autocomplete pipeline", () => { + + for (const noteCount of [500, 2000, 5000, 10000]) { + it(`granular breakdown with ${noteCount} notes`, () => { + const timings: TimingEntry[] = []; + + const [, buildMs] = timed(() => buildDataset(noteCount, { + matchFraction: 0.2, + contentWordCount: 300, + depth: 5 + })); + timings.push({ label: `Dataset build (${noteCount} notes)`, ms: buildMs }); + + // === NoteFlatTextExp: getCandidateNotes === + // This calls getFlatText() + normalizeSearchText() for EVERY note + const allNotes = Object.values(becca.notes); + for (const n of allNotes) n.invalidateThisCache(); + + const [, candidateMs] = timed(() => { + const token = normalizeSearchText("target"); + let count = 0; + for (const n of allNotes) { + const flatText = normalizeSearchText(n.getFlatText()); + if (flatText.includes(token)) count++; + } + return count; + }); + timings.push({ label: `getCandidateNotes simulation (cold caches)`, ms: candidateMs }); + + // Warm cache version + const [candidateCount, candidateWarmMs] = timed(() => { + const token = normalizeSearchText("target"); + let count = 0; + for (const n of allNotes) { + const flatText = normalizeSearchText(n.getFlatText()); + if (flatText.includes(token)) count++; + } + return count; + }); + timings.push({ label: `getCandidateNotes simulation (warm caches)`, ms: candidateWarmMs }); + + // === getBestNotePath for each candidate === + const candidates = allNotes.filter(n => { + const flatText = normalizeSearchText(n.getFlatText()); + return flatText.includes("target"); + }); + + const [, pathMs] = timed(() => { + for (const n of candidates) { + n.getBestNotePath(); + } + }); + timings.push({ label: `getBestNotePath (${candidates.length} notes)`, ms: pathMs }); + + // === SearchResult construction (includes getNoteTitleForPath) === + const paths = candidates.map(n => n.getBestNotePath()).filter(Boolean); + + const [searchResults, srMs] = timed(() => { + return paths.map(p => new SearchResult(p)); + }); + timings.push({ label: `SearchResult construction (${paths.length} results)`, ms: srMs }); + + // === computeScore === + const [, scoreMs] = timed(() => { + for (const r of searchResults) { + r.computeScore("target", ["target"], true); + } + }); + timings.push({ label: `computeScore with fuzzy (${searchResults.length} results)`, ms: scoreMs }); + + const [, scoreNoFuzzyMs] = timed(() => { + for (const r of searchResults) { + r.computeScore("target", ["target"], false); + } + }); + timings.push({ label: `computeScore no-fuzzy`, ms: scoreNoFuzzyMs }); + + // === Sorting === + const [, sortMs] = timed(() => { + searchResults.sort((a, b) => { + if (a.score !== b.score) return b.score - a.score; + if (a.notePathArray.length === b.notePathArray.length) { + return a.notePathTitle < b.notePathTitle ? -1 : 1; + } + return a.notePathArray.length - b.notePathArray.length; + }); + }); + timings.push({ label: `Sort results`, ms: sortMs }); + + // === Trim + content snippet extraction === + const trimmed = searchResults.slice(0, 200); + + const [, snippetMs] = timed(() => { + for (const r of trimmed) { + r.contentSnippet = searchService.extractContentSnippet( + r.noteId, ["target"] + ); + } + }); + timings.push({ label: `Content snippet extraction (${trimmed.length} results)`, ms: snippetMs }); + + const [, attrMs] = timed(() => { + for (const r of trimmed) { + r.attributeSnippet = searchService.extractAttributeSnippet( + r.noteId, ["target"] + ); + } + }); + timings.push({ label: `Attribute snippet extraction`, ms: attrMs }); + + // === Highlighting === + const [, hlMs] = timed(() => { + searchService.highlightSearchResults(trimmed, ["target"]); + }); + timings.push({ label: `Highlighting`, ms: hlMs }); + + // === Final mapping (getNoteTitleAndIcon) === + const [, mapMs] = timed(() => { + for (const r of trimmed) { + beccaService.getNoteTitleAndIcon(r.noteId); + } + }); + timings.push({ label: `getNoteTitleAndIcon (${trimmed.length} results)`, ms: mapMs }); + + // === Full autocomplete for comparison === + const [autoResults, autoMs] = timed(() => { + return searchService.searchNotesForAutocomplete("target", true); + }); + timings.push({ label: `Full autocomplete call (end-to-end)`, ms: autoMs }); + + reportTimings(`Granular Autocomplete — ${noteCount} notes`, timings); + expect(autoResults.length).toBeGreaterThan(0); + }); + } + }); + + /** + * Test the specific cost of normalizeSearchText which is called + * pervasively throughout the pipeline. + */ + describe("normalizeSearchText cost", () => { + + it("profile normalizeSearchText at scale", () => { + buildDataset(5000, { matchFraction: 0.2, contentWordCount: 100 }); + + // Generate various text lengths to profile + const shortTexts = Array.from({ length: 5000 }, () => randomWord(10)); + const mediumTexts = Array.from({ length: 5000 }, () => + Array.from({ length: 20 }, () => randomWord(6)).join(" ") + ); + const longTexts = Object.values(becca.notes).map(n => n.getFlatText()); + + console.log("\n=== normalizeSearchText cost ==="); + + const [, shortMs] = timed(() => { + for (const t of shortTexts) normalizeSearchText(t); + }); + console.log(` 5000 short texts (10 chars): ${shortMs.toFixed(1)}ms (${(shortMs/5000*1000).toFixed(1)}µs/call)`); + + const [, medMs] = timed(() => { + for (const t of mediumTexts) normalizeSearchText(t); + }); + console.log(` 5000 medium texts (120 chars): ${medMs.toFixed(1)}ms (${(medMs/5000*1000).toFixed(1)}µs/call)`); + + const [, longMs] = timed(() => { + for (const t of longTexts) normalizeSearchText(t); + }); + console.log(` ${longTexts.length} flat texts (varying): ${longMs.toFixed(1)}ms (${(longMs/longTexts.length*1000).toFixed(1)}µs/call)`); + }); + }); + + /** + * Test the searchPathTowardsRoot recursive walk which runs + * for every candidate note in NoteFlatTextExp. + */ + describe("searchPathTowardsRoot cost", () => { + + it("profile recursive walk with varying hierarchy depth", () => { + console.log("\n=== Search path walk vs hierarchy depth ==="); + + for (const depth of [3, 5, 8, 12]) { + buildDataset(2000, { + matchFraction: 0.15, + depth, + contentWordCount: 50 + }); + + const [results, ms] = timed(() => { + const ctx = new SearchContext({ fastSearch: true }); + return searchService.findResultsWithQuery("target", ctx); + }); + console.log(` depth=${depth}: ${ms.toFixed(1)}ms (${results.length} results)`); + } + }); + }); + + /** + * Content snippet extraction scaling — the operation that calls + * note.getContent() for each result. + */ + describe("Content snippet extraction", () => { + + it("profile snippet extraction with varying content sizes", () => { + console.log("\n=== Content snippet extraction vs content size ==="); + + for (const wordCount of [50, 200, 500, 1000, 2000, 5000]) { + buildDataset(500, { + matchFraction: 0.5, + contentWordCount: wordCount + }); + + const ctx = new SearchContext({ fastSearch: true }); + const results = searchService.findResultsWithQuery("target", ctx); + const trimmed = results.slice(0, 200); + + const [, ms] = timed(() => { + for (const r of trimmed) { + r.contentSnippet = searchService.extractContentSnippet( + r.noteId, ["target"] + ); + } + }); + + const avgContentLen = Object.values(syntheticContent) + .slice(0, 100) + .reduce((s, c) => s + c.length, 0) / 100; + + console.log(` ${String(wordCount).padStart(5)} words/note (avg ${Math.round(avgContentLen)} chars) × ${trimmed.length} results: ${ms.toFixed(1)}ms (${(ms / trimmed.length).toFixed(3)}ms/note)`); + } + }); + + it("profile snippet extraction with varying result counts", () => { + console.log("\n=== Content snippet extraction vs result count ==="); + + buildDataset(2000, { + matchFraction: 0.5, + contentWordCount: 500 + }); + + const ctx = new SearchContext({ fastSearch: true }); + const allResults = searchService.findResultsWithQuery("target", ctx); + + for (const count of [5, 10, 20, 50, 100, 200]) { + const subset = allResults.slice(0, count); + + const [, ms] = timed(() => { + for (const r of subset) { + r.contentSnippet = searchService.extractContentSnippet( + r.noteId, ["target"] + ); + } + }); + + console.log(` ${String(count).padStart(3)} results: ${ms.toFixed(1)}ms (${(ms / count).toFixed(3)}ms/note)`); + } + }); + }); + + /** + * Two-phase exact/fuzzy search cost. + */ + describe("Two-phase search cost", () => { + + for (const noteCount of [1000, 5000, 10000]) { + it(`exact vs progressive with ${noteCount} notes`, () => { + const timings: TimingEntry[] = []; + + buildDataset(noteCount, { matchFraction: 0.005, contentWordCount: 50 }); + + const [exactR, exactMs] = timed(() => { + const ctx = new SearchContext({ fastSearch: true }); + ctx.enableFuzzyMatching = false; + return searchService.findResultsWithQuery("target", ctx); + }); + timings.push({ label: `Exact-only (${exactR.length} results)`, ms: exactMs }); + + const [progR, progMs] = timed(() => { + const ctx = new SearchContext({ fastSearch: true }); + return searchService.findResultsWithQuery("target", ctx); + }); + timings.push({ label: `Progressive exact→fuzzy (${progR.length} results)`, ms: progMs }); + + const overhead = progMs - exactMs; + timings.push({ label: `Fuzzy phase overhead`, ms: Math.max(0, overhead) }); + + reportTimings(`Two-phase — ${noteCount} notes`, timings); + }); + } + }); + + /** + * End-to-end scaling to give the full picture. + */ + describe("End-to-end scaling", () => { + + it("autocomplete at different scales", () => { + console.log("\n=== End-to-end autocomplete scaling ==="); + console.log(" (fastSearch=true, monkeypatched getContent, no real SQL)"); + + for (const noteCount of [100, 500, 1000, 2000, 5000, 10000, 20000]) { + buildDataset(noteCount, { + matchFraction: 0.2, + contentWordCount: 300, + depth: 4 + }); + + // Warm up + searchService.searchNotesForAutocomplete("target", true); + + const times: number[] = []; + for (let i = 0; i < 3; i++) { + const [, ms] = timed(() => searchService.searchNotesForAutocomplete("target", true)); + times.push(ms); + } + + const avg = times.reduce((a, b) => a + b, 0) / times.length; + const min = Math.min(...times); + + console.log( + ` ${String(noteCount).padStart(6)} notes: avg ${avg.toFixed(1)}ms ` + + `min ${min.toFixed(1)}ms` + ); + } + }); + + it("compare fast vs non-fast search", () => { + console.log("\n=== Fast vs non-fast search (no real SQL for content) ==="); + + for (const noteCount of [500, 2000, 5000]) { + buildDataset(noteCount, { + matchFraction: 0.2, + contentWordCount: 200, + depth: 4 + }); + + const [, fastMs] = timed(() => { + const ctx = new SearchContext({ fastSearch: true }); + return searchService.findResultsWithQuery("target", ctx); + }); + + // Non-fast search tries NoteContentFulltextExp which uses sql.iterateRows + // This will likely fail/return empty since there's no real DB, but we + // can still measure the overhead of attempting it + let nonFastMs: number; + let nonFastCount: number; + try { + const [results, ms] = timed(() => { + const ctx = new SearchContext({ fastSearch: false }); + return searchService.findResultsWithQuery("target", ctx); + }); + nonFastMs = ms; + nonFastCount = results.length; + } catch { + nonFastMs = -1; + nonFastCount = -1; + } + + console.log( + ` ${String(noteCount).padStart(5)} notes: fast=${fastMs.toFixed(1)}ms ` + + `non-fast=${nonFastMs >= 0 ? nonFastMs.toFixed(1) + 'ms' : 'FAILED (no real DB)'} ` + + `(non-fast results: ${nonFastCount})` + ); + } + }); + }); +}); From 77733ce2050953eeb927939becfcbdf97882a2eb Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 11 Mar 2026 21:11:55 -0700 Subject: [PATCH 002/203] feat(search): try to rice performance some more --- apps/server/spec/search_profiling.spec.ts | 380 +++++++++--------- apps/server/src/becca/becca-interface.ts | 30 ++ apps/server/src/becca/entities/bnote.ts | 3 + .../search/expressions/note_flat_text.ts | 48 ++- .../src/services/search/services/search.ts | 84 ++-- 5 files changed, 318 insertions(+), 227 deletions(-) diff --git a/apps/server/spec/search_profiling.spec.ts b/apps/server/spec/search_profiling.spec.ts index 9f5f848034..8099a322b4 100644 --- a/apps/server/spec/search_profiling.spec.ts +++ b/apps/server/spec/search_profiling.spec.ts @@ -4,8 +4,8 @@ * Uses the real SQLite database (spec/db/document.db loaded in-memory), * real sql module, real becca cache, and the full app stack. * - * Seeds a large number of notes via direct SQL (much faster than ETAPI) - * to create a realistic dataset for profiling. + * Profiles search at large scale (50K+ notes) to match real-world + * performance reports from users with 240K+ notes. */ import { Application } from "express"; import { beforeAll, describe, expect, it } from "vitest"; @@ -58,224 +58,246 @@ describe("Search profiling (integration)", () => { app = await buildApp(); }); - it("seed and profile with realistic data", async () => { + it("large-scale profiling (50K notes)", async () => { const sql = (await import("../src/services/sql.js")).default; const becca = (await import("../src/becca/becca.js")).default; const beccaLoader = (await import("../src/becca/becca_loader.js")).default; const cls = (await import("../src/services/cls.js")).default; const searchService = (await import("../src/services/search/services/search.js")).default; const SearchContext = (await import("../src/services/search/search_context.js")).default; + const beccaService = (await import("../src/becca/becca_service.js")).default; await new Promise((resolve) => { cls.init(() => { const initialNoteCount = Object.keys(becca.notes).length; console.log(`\n Initial becca notes: ${initialNoteCount}`); - const configs = [ - { notes: 2000, words: 500, label: "2K notes × 500 words (~4KB)" }, - { notes: 2000, words: 2000, label: "2K notes × 2000 words (~15KB)" }, - { notes: 5000, words: 500, label: "5K notes × 500 words (~4KB)" }, - { notes: 5000, words: 2000, label: "5K notes × 2000 words (~15KB)" }, - { notes: 10000, words: 1000, label: "10K notes × 1000 words (~8KB)" }, - ]; + // ── Seed 50K notes with hierarchy ── + // Some folders (depth), some with common keyword "test" in title + const TOTAL_NOTES = 50000; + const FOLDER_COUNT = 500; // 500 folders + const NOTES_PER_FOLDER = (TOTAL_NOTES - FOLDER_COUNT) / FOLDER_COUNT; // ~99 notes per folder + const MATCH_FRACTION = 0.10; // 10% match "test" — ~5000 notes + const CONTENT_WORDS = 500; - for (const cfg of configs) { - // Reset DB: delete all seeded notes from prior iteration - sql.execute(`DELETE FROM blobs WHERE blobId LIKE 'seed%'`); - sql.execute(`DELETE FROM notes WHERE noteId LIKE 'seed%'`); - sql.execute(`DELETE FROM branches WHERE branchId LIKE 'seed%'`); + const now = new Date().toISOString().replace("T", " ").replace("Z", "+0000"); + console.log(` Seeding ${TOTAL_NOTES} notes (${FOLDER_COUNT} folders, ~${NOTES_PER_FOLDER.toFixed(0)} per folder)...`); - const TOTAL_NOTES = cfg.notes; - const MATCH_FRACTION = 0.15; - const CONTENT_WORDS = cfg.words; - const matchCount = Math.floor(TOTAL_NOTES * MATCH_FRACTION); + const [, seedMs] = timed(() => { + sql.transactional(() => { + const folderIds: string[] = []; - const now = new Date().toISOString().replace("T", " ").replace("Z", "+0000"); + // Create folders under root + for (let f = 0; f < FOLDER_COUNT; f++) { + const noteId = `seed${randomId(8)}`; + const branchId = `seed${randomId(8)}`; + const blobId = `seed${randomId(16)}`; + folderIds.push(noteId); - console.log(`\n ──── ${cfg.label} ────`); - console.log(` Seeding ${TOTAL_NOTES} notes (${matchCount} with keyword)...`); + sql.execute( + `INSERT INTO blobs (blobId, content, dateModified, utcDateModified) VALUES (?, ?, ?, ?)`, + [blobId, `

Folder ${f}

`, now, now] + ); + sql.execute( + `INSERT INTO notes (noteId, title, type, mime, blobId, isProtected, isDeleted, + dateCreated, dateModified, utcDateCreated, utcDateModified) + VALUES (?, ?, 'text', 'text/html', ?, 0, 0, ?, ?, ?, ?)`, + [noteId, `Folder ${f} ${randomWord(5)}`, blobId, now, now, now, now] + ); + sql.execute( + `INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, isDeleted, isExpanded, utcDateModified) + VALUES (?, ?, 'root', ?, 0, 0, ?)`, + [branchId, noteId, f * 10, now] + ); + } - const [, seedMs] = timed(() => { - sql.transactional(() => { - for (let i = 0; i < TOTAL_NOTES; i++) { - const isMatch = i < matchCount; + // Create notes under folders + let noteIdx = 0; + for (let f = 0; f < FOLDER_COUNT; f++) { + const parentId = folderIds[f]; + for (let n = 0; n < NOTES_PER_FOLDER; n++) { + const isMatch = noteIdx < TOTAL_NOTES * MATCH_FRACTION; const noteId = `seed${randomId(8)}`; const branchId = `seed${randomId(8)}`; const blobId = `seed${randomId(16)}`; const title = isMatch - ? `Performance Doc ${i} ${randomWord(6)}` - : `General Note ${i} ${randomWord(6)} ${randomWord(5)}`; - const content = generateContent( - CONTENT_WORDS, - isMatch ? "performance" : undefined - ); + ? `Test Document ${noteIdx} ${randomWord(6)}` + : `Note ${noteIdx} ${randomWord(6)} ${randomWord(5)}`; + const content = generateContent(CONTENT_WORDS, isMatch ? "test" : undefined); sql.execute( - `INSERT INTO blobs (blobId, content, dateModified, utcDateModified) - VALUES (?, ?, ?, ?)`, + `INSERT INTO blobs (blobId, content, dateModified, utcDateModified) VALUES (?, ?, ?, ?)`, [blobId, content, now, now] ); - sql.execute( `INSERT INTO notes (noteId, title, type, mime, blobId, isProtected, isDeleted, dateCreated, dateModified, utcDateCreated, utcDateModified) VALUES (?, ?, 'text', 'text/html', ?, 0, 0, ?, ?, ?, ?)`, [noteId, title, blobId, now, now, now, now] ); - sql.execute( - `INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, isDeleted, isExpanded, - utcDateModified) - VALUES (?, ?, 'root', ?, 0, 0, ?)`, - [branchId, noteId, i * 10, now] + `INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, isDeleted, isExpanded, utcDateModified) + VALUES (?, ?, ?, ?, 0, 0, ?)`, + [branchId, noteId, parentId, n * 10, now] ); + noteIdx++; } - }); - }); - console.log(` SQL seeding: ${seedMs.toFixed(0)}ms`); - - // Reload becca to pick up new notes - const [, reloadMs] = timed(() => { - beccaLoader.load(); - }); - console.log(` Becca reload: ${reloadMs.toFixed(0)}ms`); - console.log(` Becca notes after seed: ${Object.keys(becca.notes).length}`); - - // Verify content is accessible - const sampleNote = Object.values(becca.notes).find(n => n.title.startsWith("Performance Doc")); - if (sampleNote) { - const content = sampleNote.getContent(); - console.log(` Sample content length: ${typeof content === 'string' ? content.length : 0} chars`); - } - - // ========================================== - // PROFILING - // ========================================== - - console.log(`\n --- PROFILING (${cfg.label}) ---\n`); - - // --- 1. Fast search (NoteFlatTextExp only) --- - searchService.findResultsWithQuery("performance", new SearchContext({ fastSearch: true })); - - const fastTimes: number[] = []; - let fastResultCount = 0; - for (let i = 0; i < 5; i++) { - const [r, ms] = timed(() => - searchService.findResultsWithQuery("performance", - new SearchContext({ fastSearch: true }) - ) - ); - fastTimes.push(ms); - fastResultCount = r.length; - } - const fastAvg = fastTimes.reduce((a, b) => a + b, 0) / fastTimes.length; - console.log(` Fast search (flat text only): avg ${fastAvg.toFixed(1)}ms (${fastResultCount} results)`); - - // --- 2. Full search (flat text + content fulltext via SQL) --- - const fullTimes: number[] = []; - let fullResultCount = 0; - for (let i = 0; i < 3; i++) { - const [r, ms] = timed(() => - searchService.findResultsWithQuery("performance", - new SearchContext({ fastSearch: false }) - ) - ); - fullTimes.push(ms); - fullResultCount = r.length; - } - const fullAvg = fullTimes.reduce((a, b) => a + b, 0) / fullTimes.length; - console.log(` Full search (flat + SQL content): avg ${fullAvg.toFixed(1)}ms (${fullResultCount} results)`); - - // --- 3. Content snippet extraction --- - const fastResults = searchService.findResultsWithQuery("performance", - new SearchContext({ fastSearch: true })); - const trimmed = fastResults.slice(0, 200); - const tokens = ["performance"]; - - const snippetTimes: number[] = []; - for (let i = 0; i < 3; i++) { - const [, ms] = timed(() => { - for (const r of trimmed) { - r.contentSnippet = searchService.extractContentSnippet(r.noteId, tokens); - } - }); - snippetTimes.push(ms); - } - const snippetAvg = snippetTimes.reduce((a, b) => a + b, 0) / snippetTimes.length; - console.log(` Content snippet (${trimmed.length} results): avg ${snippetAvg.toFixed(1)}ms (${(snippetAvg / trimmed.length).toFixed(3)}ms/note)`); - - // --- 4. Raw getContent() cost --- - const contentTimes: number[] = []; - const textNotes = trimmed - .map(r => becca.notes[r.noteId]) - .filter(n => n && ["text", "code"].includes(n.type)); - - for (let i = 0; i < 5; i++) { - const [, ms] = timed(() => { - for (const n of textNotes) n.getContent(); - }); - contentTimes.push(ms); - } - const contentAvg = contentTimes.reduce((a, b) => a + b, 0) / contentTimes.length; - console.log(` getContent() × ${textNotes.length} notes: avg ${contentAvg.toFixed(1)}ms (${(contentAvg / textNotes.length).toFixed(3)}ms/note)`); - - // --- 5. striptags + normalize cost (isolated) --- - const striptags = require("striptags"); - const normalizeString = require("normalize-strings"); - const contents = textNotes.map(n => n.getContent() as string).filter(Boolean); - - const [, stripMs] = timed(() => { - for (const c of contents) { - striptags(c); } }); - console.log(` striptags × ${contents.length} notes: ${stripMs.toFixed(1)}ms (${(stripMs / contents.length).toFixed(3)}ms/note)`); + }); + console.log(` SQL seeding: ${seedMs.toFixed(0)}ms`); - const stripped = contents.map(c => striptags(c)); - const [, normMs] = timed(() => { - for (const s of stripped) { - normalizeString(s.toLowerCase()); - } - }); - console.log(` normalizeString × ${stripped.length} notes: ${normMs.toFixed(1)}ms (${(normMs / stripped.length).toFixed(3)}ms/note)`); + const [, reloadMs] = timed(() => beccaLoader.load()); + const totalNotes = Object.keys(becca.notes).length; + console.log(` Becca reload: ${reloadMs.toFixed(0)}ms Total notes: ${totalNotes}`); - // --- 6. Full autocomplete --- - const autoTimes: number[] = []; - let autoResultCount = 0; - for (let i = 0; i < 3; i++) { - const [r, ms] = timed(() => - searchService.searchNotesForAutocomplete("performance", true) - ); - autoTimes.push(ms); - autoResultCount = r.length; + // ── Warm caches ── + searchService.searchNotesForAutocomplete("test", true); + + // ════════════════════════════════════════════ + // PROFILING AT SCALE + // ════════════════════════════════════════════ + + console.log(`\n ════ PROFILING (${totalNotes} notes) ════\n`); + + // 1. getCandidateNotes cost (the full-scan bottleneck) + const allNotes = Object.values(becca.notes); + const [, flatScanMs] = timed(() => { + let count = 0; + for (const note of allNotes) { + const ft = note.getFlatText(); + if (ft.includes("test")) count++; } - const autoAvg = autoTimes.reduce((a, b) => a + b, 0) / autoTimes.length; - console.log(`\n FULL AUTOCOMPLETE: avg ${autoAvg.toFixed(1)}ms (${autoResultCount} results)`); + return count; + }); + console.log(` getFlatText + includes scan (${allNotes.length} notes): ${flatScanMs.toFixed(1)}ms`); - // --- 7. SQL content scan cost --- - const [scanCount, scanMs] = timed(() => { - let count = 0; - for (const row of sql.iterateRows<{ content: Buffer | string }>(` - SELECT noteId, type, mime, content, isProtected - FROM notes JOIN blobs USING (blobId) - WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') - AND isDeleted = 0 - AND LENGTH(content) < 2097152`)) { - count++; - } - return count; - }); - console.log(` SQL content scan (${scanCount} rows): ${scanMs.toFixed(1)}ms`); - - // --- Summary --- - console.log(`\n === SUMMARY (${cfg.label}, ${Object.keys(becca.notes).length} total notes) ===`); - console.log(` Fast search: ${fastAvg.toFixed(1)}ms`); - console.log(` Full search: ${fullAvg.toFixed(1)}ms`); - console.log(` Content snippets: ${snippetAvg.toFixed(1)}ms (${(snippetAvg / trimmed.length).toFixed(3)}ms/note)`); - console.log(` normalizeString: ${normMs.toFixed(1)}ms (${(normMs / stripped.length).toFixed(3)}ms/note)`); - console.log(` Full autocomplete: ${autoAvg.toFixed(1)}ms`); - console.log(` SQL scan: ${scanMs.toFixed(1)}ms (${scanCount} rows)`); + // 2. Full findResultsWithQuery (includes candidate scan + parent walk + scoring) + const findTimes: number[] = []; + let findResultCount = 0; + for (let i = 0; i < 3; i++) { + const [r, ms] = timed(() => + searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: true })) + ); + findTimes.push(ms); + findResultCount = r.length; } + const findAvg = findTimes.reduce((a, b) => a + b, 0) / findTimes.length; + console.log(` findResultsWithQuery (fast): avg ${findAvg.toFixed(1)}ms (${findResultCount} results)`); + + // 3. Exact-only (no fuzzy) + const exactTimes: number[] = []; + for (let i = 0; i < 3; i++) { + const [, ms] = timed(() => + searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: true, enableFuzzyMatching: false })) + ); + exactTimes.push(ms); + } + const exactAvg = exactTimes.reduce((a, b) => a + b, 0) / exactTimes.length; + console.log(` findResultsWithQuery (exact): avg ${exactAvg.toFixed(1)}ms`); + console.log(` Fuzzy overhead: ${(findAvg - exactAvg).toFixed(1)}ms`); + + // 4. SearchResult construction + computeScore cost (isolated) + const results = searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: true })); + console.log(` Total results before trim: ${results.length}`); + + const [, scoreAllMs] = timed(() => { + for (const r of results) r.computeScore("test", ["test"], true); + }); + console.log(` computeScore × ${results.length}: ${scoreAllMs.toFixed(1)}ms (${(scoreAllMs / results.length).toFixed(3)}ms/result)`); + + // 5. getNoteTitleForPath for all results + const [, pathTitleMs] = timed(() => { + for (const r of results) beccaService.getNoteTitleForPath(r.notePathArray); + }); + console.log(` getNoteTitleForPath × ${results.length}: ${pathTitleMs.toFixed(1)}ms`); + + // 6. Content snippet extraction (only 200) + const trimmed = results.slice(0, 200); + const [, snippetMs] = timed(() => { + for (const r of trimmed) { + r.contentSnippet = searchService.extractContentSnippet(r.noteId, ["test"]); + } + }); + console.log(` extractContentSnippet × 200: ${snippetMs.toFixed(1)}ms`); + + // 7. Highlighting (only 200) + const [, hlMs] = timed(() => { + searchService.highlightSearchResults(trimmed, ["test"]); + }); + console.log(` highlightSearchResults × 200: ${hlMs.toFixed(1)}ms`); + + // 7b. getBestNotePath cost (used by fast path) + const sampleNotes = Object.values(becca.notes).filter(n => n.title.startsWith("Test Document")).slice(0, 1000); + const [, bestPathMs] = timed(() => { + for (const n of sampleNotes) n.getBestNotePath(); + }); + console.log(` getBestNotePath × ${sampleNotes.length}: ${bestPathMs.toFixed(1)}ms (${(bestPathMs/sampleNotes.length).toFixed(3)}ms/note)`); + + // 8. Full autocomplete end-to-end + const autoTimes: number[] = []; + let autoCount = 0; + for (let i = 0; i < 3; i++) { + const [r, ms] = timed(() => + searchService.searchNotesForAutocomplete("test", true) + ); + autoTimes.push(ms); + autoCount = r.length; + } + const autoAvg = autoTimes.reduce((a, b) => a + b, 0) / autoTimes.length; + const autoMin = Math.min(...autoTimes); + console.log(`\n ★ FULL AUTOCOMPLETE: avg ${autoAvg.toFixed(1)}ms min ${autoMin.toFixed(1)}ms (${autoCount} results)`); + + // 9. With a less common search term (fewer matches) + const rareTimes: number[] = []; + let rareCount = 0; + for (let i = 0; i < 3; i++) { + const [r, ms] = timed(() => + searchService.searchNotesForAutocomplete("leitfaden", true) + ); + rareTimes.push(ms); + rareCount = r.length; + } + const rareAvg = rareTimes.reduce((a, b) => a + b, 0) / rareTimes.length; + console.log(` Autocomplete "leitfaden": avg ${rareAvg.toFixed(1)}ms (${rareCount} results)`); + + // 10. Full search (fastSearch=false) — the 2.7s bottleneck + console.log(`\n ── Full search (fastSearch=false) ──`); + const fullTimes: number[] = []; + let fullCount = 0; + for (let i = 0; i < 2; i++) { + const [r, ms] = timed(() => + searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: false })) + ); + fullTimes.push(ms); + fullCount = r.length; + } + const fullAvg = fullTimes.reduce((a, b) => a + b, 0) / fullTimes.length; + console.log(` Full search (flat + SQL): avg ${fullAvg.toFixed(1)}ms (${fullCount} results)`); + + // 11. SQL content scan alone + const [scanCount, scanMs] = timed(() => { + let count = 0; + for (const row of sql.iterateRows<{ content: Buffer | string }>(` + SELECT noteId, type, mime, content, isProtected + FROM notes JOIN blobs USING (blobId) + WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') + AND isDeleted = 0 + AND LENGTH(content) < 2097152`)) { + count++; + } + return count; + }); + console.log(` Raw SQL scan (${scanCount} rows): ${scanMs.toFixed(1)}ms`); + + // ── Summary ── + console.log(`\n ════ SUMMARY ════`); + console.log(` Notes: ${totalNotes} | Matches: ${findResultCount} | Hierarchy depth: 3 (root → folder → note)`); + console.log(` ──────────────────────────────────`); + console.log(` Autocomplete (fast): ${autoAvg.toFixed(1)}ms`); + console.log(` findResults: ${findAvg.toFixed(1)}ms (${((findAvg/autoAvg)*100).toFixed(0)}%)`); + console.log(` snippets+highlight: ${(snippetMs + hlMs).toFixed(1)}ms (${(((snippetMs+hlMs)/autoAvg)*100).toFixed(0)}%)`); + console.log(` Full search: ${fullAvg.toFixed(1)}ms`); resolve(); }); diff --git a/apps/server/src/becca/becca-interface.ts b/apps/server/src/becca/becca-interface.ts index 1a8203f436..6619ed30b9 100644 --- a/apps/server/src/becca/becca-interface.ts +++ b/apps/server/src/becca/becca-interface.ts @@ -31,9 +31,17 @@ export default class Becca { allNoteSetCache: NoteSet | null; + /** + * Pre-built parallel arrays for fast flat text scanning in search. + * Avoids per-note property access overhead when iterating 50K+ notes. + * Dirtied when notes change (along with allNoteSetCache). + */ + flatTextIndex: { notes: BNote[], flatTexts: string[] } | null; + constructor() { this.reset(); this.allNoteSetCache = null; + this.flatTextIndex = null; } reset() { @@ -239,6 +247,28 @@ export default class Becca { /** Should be called when the set of all non-skeleton notes changes (added/removed) */ dirtyNoteSetCache() { this.allNoteSetCache = null; + this.flatTextIndex = null; + } + + /** + * Returns pre-built parallel arrays of notes and their flat texts for fast scanning. + * The flat texts are already normalized (lowercase, diacritics removed). + */ + getFlatTextIndex(): { notes: BNote[], flatTexts: string[] } { + if (!this.flatTextIndex) { + const allNoteSet = this.getAllNoteSet(); + const notes: BNote[] = []; + const flatTexts: string[] = []; + + for (const note of allNoteSet.notes) { + notes.push(note); + flatTexts.push(note.getFlatText()); + } + + this.flatTextIndex = { notes, flatTexts }; + } + + return this.flatTextIndex; } getAllNoteSet() { diff --git a/apps/server/src/becca/entities/bnote.ts b/apps/server/src/becca/entities/bnote.ts index 112543a603..4e78974b4e 100644 --- a/apps/server/src/becca/entities/bnote.ts +++ b/apps/server/src/becca/entities/bnote.ts @@ -790,6 +790,9 @@ class BNote extends AbstractBeccaEntity { this.__attributeCache = null; this.__inheritableAttributeCache = null; this.__ancestorCache = null; + + // Dirty the becca-level flat text index since this note's flat text may have changed + this.becca.flatTextIndex = null; } invalidateSubTree(path: string[] = []) { diff --git a/apps/server/src/services/search/expressions/note_flat_text.ts b/apps/server/src/services/search/expressions/note_flat_text.ts index b9ad19c36c..93213d164e 100644 --- a/apps/server/src/services/search/expressions/note_flat_text.ts +++ b/apps/server/src/services/search/expressions/note_flat_text.ts @@ -99,6 +99,22 @@ class NoteFlatTextExp extends Expression { const candidateNotes = this.getCandidateNotes(inputNoteSet, searchContext); + // Fast path for single-token searches with a limit (e.g. autocomplete): + // Skip the expensive recursive parent walk and just use getBestNotePath(). + // The flat text already matched, so we know the token is present. + if (this.tokens.length === 1 && searchContext.limit) { + for (const note of candidateNotes) { + if (!resultNoteSet.hasNoteId(note.noteId)) { + const notePath = note.getBestNotePath(); + if (notePath) { + executionContext.noteIdToNotePath[note.noteId] = notePath; + resultNoteSet.add(note); + } + } + } + return resultNoteSet; + } + for (const note of candidateNotes) { // autocomplete should be able to find notes by their noteIds as well (only leafs) if (this.tokens.length === 1 && note.noteId.toLowerCase() === this.tokens[0]) { @@ -112,7 +128,7 @@ class NoteFlatTextExp extends Expression { // Add defensive checks for undefined properties const typeMatches = note.type && note.type.includes(token); const mimeMatches = note.mime && note.mime.includes(token); - + if (typeMatches || mimeMatches) { foundAttrTokens.push(token); } @@ -165,14 +181,38 @@ class NoteFlatTextExp extends Expression { getCandidateNotes(noteSet: NoteSet, searchContext?: SearchContext): BNote[] { const candidateNotes: BNote[] = []; - for (const note of noteSet.notes) { - const normalizedFlatText = normalizeSearchText(note.getFlatText()); + // For limited searches (e.g. autocomplete), cap candidates to avoid + // processing thousands of matches when only a few hundred are needed. + // Use 5x the limit to ensure enough quality candidates for scoring. + const maxCandidates = searchContext?.limit ? searchContext.limit * 5 : Infinity; + + // Use the pre-built flat text index for fast scanning. + // This provides pre-computed flat texts in a parallel array, avoiding + // per-note property access overhead at large scale (50K+ notes). + const { notes: indexNotes, flatTexts } = becca.getFlatTextIndex(); + + // Build a set for quick membership check when noteSet isn't the full set + const isFullSet = noteSet.notes.length === indexNotes.length; + + for (let i = 0; i < indexNotes.length; i++) { + const note = indexNotes[i]; + + // Skip notes not in the input set (only check when not using the full set) + if (!isFullSet && !noteSet.hasNoteId(note.noteId)) { + continue; + } + + const flatText = flatTexts[i]; for (const token of this.tokens) { - if (this.smartMatch(normalizedFlatText, token, searchContext)) { + if (this.smartMatch(flatText, token, searchContext)) { candidateNotes.push(note); break; } } + + if (candidateNotes.length >= maxCandidates) { + break; + } } return candidateNotes; diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index 4701964f5b..7ee3e494f4 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -16,7 +16,6 @@ import type { SearchParams, TokenStructure } from "./types.js"; import type Expression from "../expressions/expression.js"; import sql from "../../sql.js"; import scriptService from "../../script.js"; -import striptags from "striptags"; import protectedSessionService from "../../protected_session.js"; export interface SearchNoteResult { @@ -249,23 +248,30 @@ function findResultsWithExpression(expression: Expression, searchContext: Search return performSearch(expression, searchContext, false); } + // For limited searches (e.g. autocomplete), skip the expensive two-phase + // fuzzy fallback. The user is typing and will refine their query — exact + // matching is sufficient and avoids a second full scan of all notes. + if (searchContext.limit) { + return performSearch(expression, searchContext, false); + } + // Phase 1: Try exact matches first (without fuzzy matching) const exactResults = performSearch(expression, searchContext, false); - + // Check if we have sufficient high-quality results const minResultThreshold = 5; const minScoreForQuality = 10; // Minimum score to consider a result "high quality" - + const highQualityResults = exactResults.filter(result => result.score >= minScoreForQuality); - + // If we have enough high-quality exact matches, return them if (highQualityResults.length >= minResultThreshold) { return exactResults; } - + // Phase 2: Add fuzzy matching as fallback when exact matches are insufficient const fuzzyResults = performSearch(expression, searchContext, true); - + // Merge results, ensuring exact matches always rank higher than fuzzy matches return mergeExactAndFuzzyResults(exactResults, fuzzyResults); } @@ -447,7 +453,7 @@ function extractContentSnippet(noteId: string, searchTokens: string[], maxLength try { let content = note.getContent(); - + if (!content || typeof content !== "string") { return ""; } @@ -463,77 +469,66 @@ function extractContentSnippet(noteId: string, searchTokens: string[], maxLength return ""; // Protected but no session available } - // Strip HTML tags for text notes + // Strip HTML tags for text notes — use fast regex for snippet extraction + // (striptags library is ~18x slower and not needed for search snippets) if (note.type === "text") { - content = striptags(content); + content = content.replace(/<[^>]*>/g, ""); } - // Normalize whitespace while preserving paragraph breaks - // First, normalize multiple newlines to double newlines (paragraph breaks) - content = content.replace(/\n\s*\n/g, "\n\n"); - // Then normalize spaces within lines - content = content.split('\n').map(line => line.replace(/\s+/g, " ").trim()).join('\n'); - // Finally trim the whole content - content = content.trim(); - if (!content) { return ""; } - // Try to find a snippet around the first matching token + // Find match position using normalize on the raw stripped content. + // We use a single normalize() pass — no need for expensive whitespace + // normalization just to find the match index. const normalizedContent = normalize(content); + const normalizedTokens = searchTokens.map(token => normalize(token)); let snippetStart = 0; - let matchFound = false; - for (const token of searchTokens) { - const normalizedToken = normalize(token); + for (const normalizedToken of normalizedTokens) { const matchIndex = normalizedContent.indexOf(normalizedToken); - + if (matchIndex !== -1) { // Center the snippet around the match snippetStart = Math.max(0, matchIndex - maxLength / 2); - matchFound = true; break; } } - // Extract snippet - let snippet = content.substring(snippetStart, snippetStart + maxLength); + // Extract a snippet region from the raw content, then clean only that + const snippetRegion = content.substring(snippetStart, snippetStart + maxLength + 100); - // If snippet contains linebreaks, limit to max 4 lines and override character limit + // Normalize whitespace only on the small snippet region + let snippet = snippetRegion + .replace(/\n\s*\n/g, "\n\n") + .replace(/[ \t]+/g, " ") + .trim() + .substring(0, maxLength); + + // If snippet contains linebreaks, limit to max 4 lines const lines = snippet.split('\n'); if (lines.length > 4) { - // Find which lines contain the search tokens to ensure they're included - const normalizedLines = lines.map(line => normalize(line)); - const normalizedTokens = searchTokens.map(token => normalize(token)); - // Find the first line that contains a search token let firstMatchLine = -1; - for (let i = 0; i < normalizedLines.length; i++) { - if (normalizedTokens.some(token => normalizedLines[i].includes(token))) { + for (let i = 0; i < lines.length; i++) { + const normalizedLine = normalize(lines[i]); + if (normalizedTokens.some(token => normalizedLine.includes(token))) { firstMatchLine = i; break; } } if (firstMatchLine !== -1) { - // Center the 4-line window around the first match - // Try to show 1 line before and 2 lines after the match const startLine = Math.max(0, firstMatchLine - 1); const endLine = Math.min(lines.length, startLine + 4); snippet = lines.slice(startLine, endLine).join('\n'); } else { - // No match found in lines (shouldn't happen), just take first 4 snippet = lines.slice(0, 4).join('\n'); } - // Add ellipsis if we truncated lines snippet = snippet + "..."; - } else if (lines.length > 1) { - // For multi-line snippets that are 4 or fewer lines, keep them as-is - // No need to truncate - } else { - // Single line content - apply original word boundary logic - // Try to start/end at word boundaries + } else if (lines.length <= 1) { + // Single line content - apply word boundary logic if (snippetStart > 0) { const firstSpace = snippet.search(/\s/); if (firstSpace > 0 && firstSpace < 20) { @@ -541,7 +536,7 @@ function extractContentSnippet(noteId: string, searchTokens: string[], maxLength } snippet = "..." + snippet; } - + if (snippetStart + maxLength < content.length) { const lastSpace = snippet.search(/\s[^\s]*$/); if (lastSpace > snippet.length - 20 && lastSpace > 0) { @@ -649,7 +644,8 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) { includeHiddenNotes: true, fuzzyAttributeSearch: true, ignoreInternalAttributes: true, - ancestorNoteId: hoistedNoteService.isHoistedInHiddenSubtree() ? "root" : hoistedNoteService.getHoistedNoteId() + ancestorNoteId: hoistedNoteService.isHoistedInHiddenSubtree() ? "root" : hoistedNoteService.getHoistedNoteId(), + limit: 200 }); const allSearchResults = findResultsWithQuery(query, searchContext); From 6a06fc79956914f1b437dd5131886ccaa14fb60c Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Thu, 12 Mar 2026 14:02:23 -0700 Subject: [PATCH 003/203] feat(search): get rid of candidate capping --- .../src/services/search/expressions/note_flat_text.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/server/src/services/search/expressions/note_flat_text.ts b/apps/server/src/services/search/expressions/note_flat_text.ts index 93213d164e..eff3622a76 100644 --- a/apps/server/src/services/search/expressions/note_flat_text.ts +++ b/apps/server/src/services/search/expressions/note_flat_text.ts @@ -181,11 +181,6 @@ class NoteFlatTextExp extends Expression { getCandidateNotes(noteSet: NoteSet, searchContext?: SearchContext): BNote[] { const candidateNotes: BNote[] = []; - // For limited searches (e.g. autocomplete), cap candidates to avoid - // processing thousands of matches when only a few hundred are needed. - // Use 5x the limit to ensure enough quality candidates for scoring. - const maxCandidates = searchContext?.limit ? searchContext.limit * 5 : Infinity; - // Use the pre-built flat text index for fast scanning. // This provides pre-computed flat texts in a parallel array, avoiding // per-note property access overhead at large scale (50K+ notes). @@ -210,10 +205,7 @@ class NoteFlatTextExp extends Expression { } } - if (candidateNotes.length >= maxCandidates) { - break; - } - } +} return candidateNotes; } From 9403efa9a1a99be6afe365eaf4fff1859555a760 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Thu, 12 Mar 2026 14:21:36 -0700 Subject: [PATCH 004/203] feat(search): add even some more robust tests --- .../search/services/search_profiling.spec.ts | 151 +++++++++++++++++- 1 file changed, 145 insertions(+), 6 deletions(-) diff --git a/apps/server/src/services/search/services/search_profiling.spec.ts b/apps/server/src/services/search/services/search_profiling.spec.ts index 96a414b257..6ed5d9fbb7 100644 --- a/apps/server/src/services/search/services/search_profiling.spec.ts +++ b/apps/server/src/services/search/services/search_profiling.spec.ts @@ -33,9 +33,10 @@ function randomWord(len = 6): string { return word; } -function generateHtmlContent(wordCount: number, includeTarget = false): string { +function generateHtmlContent(wordCount: number, includeKeywords = false, keywords?: string[]): string { const paragraphs: string[] = []; let wordsRemaining = wordCount; + const kws = keywords ?? ["target"]; while (wordsRemaining > 0) { const paraWords = Math.min(wordsRemaining, 20 + Math.floor(Math.random() * 40)); @@ -43,8 +44,12 @@ function generateHtmlContent(wordCount: number, includeTarget = false): string { for (let i = 0; i < paraWords; i++) { words.push(randomWord(3 + Math.floor(Math.random() * 10))); } - if (includeTarget && paragraphs.length === 2) { - words[Math.floor(words.length / 2)] = "target"; + if (includeKeywords && paragraphs.length === 2) { + // Inject all keywords into the paragraph at spaced positions + for (let k = 0; k < kws.length; k++) { + const pos = Math.min(words.length - 1, Math.floor((words.length / (kws.length + 1)) * (k + 1))); + words[pos] = kws[k]; + } } paragraphs.push(`

${words.join(" ")}

`); wordsRemaining -= paraWords; @@ -80,12 +85,21 @@ function buildDataset(noteCount: number, opts: { labelsPerNote?: number; depth?: number; contentWordCount?: number; + /** When set, contentWordCount is treated as a median and actual sizes vary from 0.2x to 3x */ + varyContentSize?: boolean; + /** Keywords to inject into matching notes' titles (default: ["target"]) */ + titleKeywords?: string[]; + /** Keywords to inject into matching notes' content (default: same as titleKeywords) */ + contentKeywords?: string[]; } = {}) { const { matchFraction = 0.1, labelsPerNote = 3, depth = 3, contentWordCount = 200, + varyContentSize = false, + titleKeywords = ["target"], + contentKeywords = titleKeywords, } = opts; becca.reset(); @@ -115,18 +129,39 @@ function buildDataset(noteCount: number, opts: { for (let i = 0; i < noteCount; i++) { const isMatch = i < matchCount; const title = isMatch - ? `${randomWord(5)} target ${randomWord(5)} Document ${i}` + ? `${randomWord(5)} ${titleKeywords.join(" ")} ${randomWord(5)} Document ${i}` : `${randomWord(5)} ${randomWord(6)} ${randomWord(4)} Note ${i}`; const n = note(title); for (let l = 0; l < labelsPerNote; l++) { const labelName = isMatch && l === 0 ? "category" : `label_${randomWord(4)}`; - const labelValue = isMatch && l === 0 ? "important target" : randomWord(8); + const labelValue = isMatch && l === 0 ? `important ${titleKeywords[0]}` : randomWord(8); n.label(labelName, labelValue); } - syntheticContent[n.note.noteId] = generateHtmlContent(contentWordCount, isMatch); + // Vary content size: 0.2x to 3x the median, producing a realistic + // mix of short stubs, medium notes, and long documents. + let noteWordCount = contentWordCount; + if (varyContentSize) { + const r = Math.random(); + if (r < 0.2) { + noteWordCount = Math.floor(contentWordCount * (0.2 + Math.random() * 0.3)); // 20-50% (short stubs) + } else if (r < 0.7) { + noteWordCount = Math.floor(contentWordCount * (0.7 + Math.random() * 0.6)); // 70-130% (medium) + } else if (r < 0.9) { + noteWordCount = Math.floor(contentWordCount * (1.3 + Math.random() * 0.7)); // 130-200% (long) + } else { + noteWordCount = Math.floor(contentWordCount * (2.0 + Math.random() * 1.0)); // 200-300% (very long) + } + } + + const includeContentKeyword = isMatch && contentKeywords.length > 0; + syntheticContent[n.note.noteId] = generateHtmlContent( + noteWordCount, + includeContentKeyword, + includeContentKeyword ? contentKeywords : undefined + ); const containerIndex = i % containers.length; containers[containerIndex].child(n); @@ -451,6 +486,110 @@ describe("Search Profiling", () => { /** * End-to-end scaling to give the full picture. */ + /** + * Multi-token search with varying content sizes. + * Real users search things like "meeting notes january" — this exercises + * the multi-token path (which doesn't use the single-token fast path) + * with a realistic mix of note sizes. + */ + describe("Multi-token search with varying content sizes", () => { + + it("single vs multi-token autocomplete at scale", () => { + console.log("\n=== Single vs multi-token autocomplete (varying content sizes) ==="); + + for (const noteCount of [1000, 5000, 10000, 20000]) { + buildDataset(noteCount, { + matchFraction: 0.15, + contentWordCount: 400, + varyContentSize: true, + depth: 5, + titleKeywords: ["meeting", "notes", "january"], + contentKeywords: ["meeting", "notes", "january"], + }); + + // Warm up + searchService.searchNotesForAutocomplete("meeting", true); + + // Single token + const singleTimes: number[] = []; + for (let i = 0; i < 3; i++) { + const [, ms] = timed(() => searchService.searchNotesForAutocomplete("meeting", true)); + singleTimes.push(ms); + } + const singleAvg = singleTimes.reduce((a, b) => a + b, 0) / singleTimes.length; + + // Two tokens + const twoTimes: number[] = []; + for (let i = 0; i < 3; i++) { + const [, ms] = timed(() => searchService.searchNotesForAutocomplete("meeting notes", true)); + twoTimes.push(ms); + } + const twoAvg = twoTimes.reduce((a, b) => a + b, 0) / twoTimes.length; + + // Three tokens + const threeTimes: number[] = []; + for (let i = 0; i < 3; i++) { + const [, ms] = timed(() => searchService.searchNotesForAutocomplete("meeting notes january", true)); + threeTimes.push(ms); + } + const threeAvg = threeTimes.reduce((a, b) => a + b, 0) / threeTimes.length; + + console.log( + ` ${String(noteCount).padStart(6)} notes: ` + + `1-token ${singleAvg.toFixed(1)}ms ` + + `2-token ${twoAvg.toFixed(1)}ms ` + + `3-token ${threeAvg.toFixed(1)}ms` + ); + } + }); + + it("multi-token with realistic content size distribution", () => { + console.log("\n=== Multi-token search — content size distribution ==="); + + buildDataset(5000, { + matchFraction: 0.15, + contentWordCount: 400, + varyContentSize: true, + depth: 5, + titleKeywords: ["project", "review"], + contentKeywords: ["project", "review"], + }); + + // Report the actual content size distribution + const sizes = Object.values(syntheticContent).map(c => c.length); + sizes.sort((a, b) => a - b); + const p10 = sizes[Math.floor(sizes.length * 0.1)]; + const p50 = sizes[Math.floor(sizes.length * 0.5)]; + const p90 = sizes[Math.floor(sizes.length * 0.9)]; + const p99 = sizes[Math.floor(sizes.length * 0.99)]; + console.log(` Content sizes: p10=${p10} p50=${p50} p90=${p90} p99=${p99} chars`); + + // Warm up + searchService.searchNotesForAutocomplete("project", true); + + const queries = [ + "project", + "project review", + "project review document", + `${randomWord(7)}`, // no-match single token + `${randomWord(5)} ${randomWord(6)}`, // no-match multi token + ]; + + for (const query of queries) { + const times: number[] = []; + let resultCount = 0; + for (let i = 0; i < 3; i++) { + const [r, ms] = timed(() => searchService.searchNotesForAutocomplete(query, true)); + times.push(ms); + resultCount = r.length; + } + const avg = times.reduce((a, b) => a + b, 0) / times.length; + const label = `"${query}"`.padEnd(35); + console.log(` ${label} ${avg.toFixed(1)}ms (${resultCount} results)`); + } + }); + }); + describe("End-to-end scaling", () => { it("autocomplete at different scales", () => { From 1c148f407cc8c397374b87575c984c15f0fae1da Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Thu, 12 Mar 2026 14:35:17 -0700 Subject: [PATCH 005/203] feat(search): don't toss the entire index after each note change --- apps/server/src/becca/becca-interface.ts | 46 +++++++++++++++++-- apps/server/src/becca/entities/battribute.ts | 10 ++++ apps/server/src/becca/entities/bnote.ts | 4 +- .../search/expressions/note_flat_text.ts | 9 ++-- 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/apps/server/src/becca/becca-interface.ts b/apps/server/src/becca/becca-interface.ts index 6619ed30b9..1a95408d9b 100644 --- a/apps/server/src/becca/becca-interface.ts +++ b/apps/server/src/becca/becca-interface.ts @@ -34,14 +34,19 @@ export default class Becca { /** * Pre-built parallel arrays for fast flat text scanning in search. * Avoids per-note property access overhead when iterating 50K+ notes. - * Dirtied when notes change (along with allNoteSetCache). + * Supports incremental updates: when individual notes change, only their + * entries are rebuilt rather than the entire index. */ - flatTextIndex: { notes: BNote[], flatTexts: string[] } | null; + flatTextIndex: { notes: BNote[], flatTexts: string[], noteIdToIdx: Map } | null; + + /** NoteIds whose flat text needs to be recomputed in the index. */ + dirtyFlatTextNoteIds: Set; constructor() { - this.reset(); + this.dirtyFlatTextNoteIds = new Set(); this.allNoteSetCache = null; this.flatTextIndex = null; + this.reset(); } reset() { @@ -247,25 +252,56 @@ export default class Becca { /** Should be called when the set of all non-skeleton notes changes (added/removed) */ dirtyNoteSetCache() { this.allNoteSetCache = null; + // Full rebuild needed since the note set itself changed this.flatTextIndex = null; + this.dirtyFlatTextNoteIds.clear(); + } + + /** Mark a single note's flat text as needing recomputation in the index. */ + dirtyNoteFlatText(noteId: string) { + if (this.flatTextIndex) { + // Index exists — schedule an incremental update + this.dirtyFlatTextNoteIds.add(noteId); + } + // If flatTextIndex is null, full rebuild will happen on next access anyway } /** * Returns pre-built parallel arrays of notes and their flat texts for fast scanning. * The flat texts are already normalized (lowercase, diacritics removed). + * Supports incremental updates: when individual notes are dirtied, only their + * entries are recomputed rather than rebuilding the entire index. */ - getFlatTextIndex(): { notes: BNote[], flatTexts: string[] } { + getFlatTextIndex(): { notes: BNote[], flatTexts: string[], noteIdToIdx: Map } { if (!this.flatTextIndex) { const allNoteSet = this.getAllNoteSet(); const notes: BNote[] = []; const flatTexts: string[] = []; + const noteIdToIdx = new Map(); for (const note of allNoteSet.notes) { + noteIdToIdx.set(note.noteId, notes.length); notes.push(note); flatTexts.push(note.getFlatText()); } - this.flatTextIndex = { notes, flatTexts }; + this.flatTextIndex = { notes, flatTexts, noteIdToIdx }; + this.dirtyFlatTextNoteIds.clear(); + } else if (this.dirtyFlatTextNoteIds.size > 0) { + // Incremental update: only recompute flat texts for dirtied notes + const { flatTexts, noteIdToIdx } = this.flatTextIndex; + + for (const noteId of this.dirtyFlatTextNoteIds) { + const idx = noteIdToIdx.get(noteId); + if (idx !== undefined) { + const note = this.notes[noteId]; + if (note) { + flatTexts[idx] = note.getFlatText(); + } + } + } + + this.dirtyFlatTextNoteIds.clear(); } return this.flatTextIndex; diff --git a/apps/server/src/becca/entities/battribute.ts b/apps/server/src/becca/entities/battribute.ts index 6ff1246fcf..77a15c2fd1 100644 --- a/apps/server/src/becca/entities/battribute.ts +++ b/apps/server/src/becca/entities/battribute.ts @@ -6,6 +6,7 @@ import dateUtils from "../../services/date_utils.js"; import promotedAttributeDefinitionParser from "../../services/promoted_attribute_definition_parser.js"; import sanitizeAttributeName from "../../services/sanitize_attribute_name.js"; import type { AttributeRow, AttributeType } from "@triliumnext/commons"; +import { normalize } from "../../services/utils.js"; interface SavingOpts { skipValidation?: boolean; @@ -34,6 +35,11 @@ class BAttribute extends AbstractBeccaEntity { value!: string; isInheritable!: boolean; + /** Pre-normalized (lowercase, diacritics removed) name for search. */ + normalizedName!: string; + /** Pre-normalized (lowercase, diacritics removed) value for search. */ + normalizedValue!: string; + constructor(row?: AttributeRow) { super(); @@ -59,6 +65,10 @@ class BAttribute extends AbstractBeccaEntity { this.isInheritable = !!isInheritable; this.utcDateModified = utcDateModified; + // Pre-compute normalized forms for search (avoids repeated normalize() calls in hot loops) + this.normalizedName = normalize(this.name); + this.normalizedValue = normalize(this.value); + return this; } diff --git a/apps/server/src/becca/entities/bnote.ts b/apps/server/src/becca/entities/bnote.ts index 4e78974b4e..10750efc32 100644 --- a/apps/server/src/becca/entities/bnote.ts +++ b/apps/server/src/becca/entities/bnote.ts @@ -791,8 +791,8 @@ class BNote extends AbstractBeccaEntity { this.__inheritableAttributeCache = null; this.__ancestorCache = null; - // Dirty the becca-level flat text index since this note's flat text may have changed - this.becca.flatTextIndex = null; + // Mark only this note's flat text as dirty for incremental index update + this.becca.dirtyNoteFlatText(this.noteId); } invalidateSubTree(path: string[] = []) { diff --git a/apps/server/src/services/search/expressions/note_flat_text.ts b/apps/server/src/services/search/expressions/note_flat_text.ts index eff3622a76..ff54287d91 100644 --- a/apps/server/src/services/search/expressions/note_flat_text.ts +++ b/apps/server/src/services/search/expressions/note_flat_text.ts @@ -7,7 +7,7 @@ import Expression from "./expression.js"; import NoteSet from "../note_set.js"; import becca from "../../../becca/becca.js"; import { normalize } from "../../utils.js"; -import { normalizeSearchText, fuzzyMatchWord, fuzzyMatchWordWithResult } from "../utils/text_utils.js"; +import { normalizeSearchText, fuzzyMatchWordWithResult } from "../utils/text_utils.js"; import beccaService from "../../../becca/becca_service.js"; class NoteFlatTextExp extends Expression { @@ -67,11 +67,8 @@ class NoteFlatTextExp extends Expression { } for (const attribute of note.getOwnedAttributes()) { - const normalizedName = normalizeSearchText(attribute.name); - const normalizedValue = normalizeSearchText(attribute.value); - for (const token of remainingTokens) { - if (normalizedName.includes(token) || normalizedValue.includes(token)) { + if (attribute.normalizedName.includes(token) || attribute.normalizedValue.includes(token)) { foundAttrTokens.push(token); } } @@ -134,7 +131,7 @@ class NoteFlatTextExp extends Expression { } for (const attribute of note.ownedAttributes) { - if (normalizeSearchText(attribute.name).includes(token) || normalizeSearchText(attribute.value).includes(token)) { + if (attribute.normalizedName.includes(token) || attribute.normalizedValue.includes(token)) { foundAttrTokens.push(token); } } From b533546236a4264d64697ae9a76fb8a9f63c25a3 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Thu, 12 Mar 2026 14:35:47 -0700 Subject: [PATCH 006/203] fix(search): fix flying bracket --- apps/server/src/services/search/expressions/note_flat_text.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/server/src/services/search/expressions/note_flat_text.ts b/apps/server/src/services/search/expressions/note_flat_text.ts index ff54287d91..b1ceac991e 100644 --- a/apps/server/src/services/search/expressions/note_flat_text.ts +++ b/apps/server/src/services/search/expressions/note_flat_text.ts @@ -201,8 +201,7 @@ class NoteFlatTextExp extends Expression { break; } } - -} + } return candidateNotes; } From 5718631889ecb8fa01e72d2c2f374ebdc0de44a7 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 18 Mar 2026 09:46:24 -0700 Subject: [PATCH 007/203] fix(search): resolve issue with autocomplete with search performance enhancements --- .../src/services/search/expressions/note_flat_text.ts | 4 ++-- apps/server/src/services/search/search_context.ts | 3 +++ apps/server/src/services/search/services/search.ts | 10 +++++----- apps/server/src/services/search/services/types.ts | 2 ++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/server/src/services/search/expressions/note_flat_text.ts b/apps/server/src/services/search/expressions/note_flat_text.ts index b1ceac991e..ef3efbf66f 100644 --- a/apps/server/src/services/search/expressions/note_flat_text.ts +++ b/apps/server/src/services/search/expressions/note_flat_text.ts @@ -96,10 +96,10 @@ class NoteFlatTextExp extends Expression { const candidateNotes = this.getCandidateNotes(inputNoteSet, searchContext); - // Fast path for single-token searches with a limit (e.g. autocomplete): + // Fast path for single-token autocomplete searches: // Skip the expensive recursive parent walk and just use getBestNotePath(). // The flat text already matched, so we know the token is present. - if (this.tokens.length === 1 && searchContext.limit) { + if (this.tokens.length === 1 && searchContext.autocomplete) { for (const note of candidateNotes) { if (!resultNoteSet.hasNoteId(note.noteId)) { const notePath = note.getBestNotePath(); diff --git a/apps/server/src/services/search/search_context.ts b/apps/server/src/services/search/search_context.ts index 314c7e7ce6..55d4df5d2f 100644 --- a/apps/server/src/services/search/search_context.ts +++ b/apps/server/src/services/search/search_context.ts @@ -18,6 +18,8 @@ class SearchContext { debug?: boolean; debugInfo: {} | null; fuzzyAttributeSearch: boolean; + /** When true, skip the two-phase fuzzy fallback and use the single-token fast path. */ + autocomplete: boolean; enableFuzzyMatching: boolean; // Controls whether fuzzy matching is enabled for this search phase highlightedTokens: string[]; originalQuery: string; @@ -46,6 +48,7 @@ class SearchContext { this.debug = params.debug; this.debugInfo = null; this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch; + this.autocomplete = !!params.autocomplete; this.enableFuzzyMatching = true; // Default to true for backward compatibility this.highlightedTokens = []; this.originalQuery = ""; diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index 7ee3e494f4..b533c185fe 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -248,10 +248,10 @@ function findResultsWithExpression(expression: Expression, searchContext: Search return performSearch(expression, searchContext, false); } - // For limited searches (e.g. autocomplete), skip the expensive two-phase - // fuzzy fallback. The user is typing and will refine their query — exact - // matching is sufficient and avoids a second full scan of all notes. - if (searchContext.limit) { + // For autocomplete searches, skip the expensive two-phase fuzzy fallback. + // The user is typing and will refine their query — exact matching is + // sufficient and avoids a second full scan of all notes. + if (searchContext.autocomplete) { return performSearch(expression, searchContext, false); } @@ -645,7 +645,7 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) { fuzzyAttributeSearch: true, ignoreInternalAttributes: true, ancestorNoteId: hoistedNoteService.isHoistedInHiddenSubtree() ? "root" : hoistedNoteService.getHoistedNoteId(), - limit: 200 + autocomplete: true }); const allSearchResults = findResultsWithQuery(query, searchContext); diff --git a/apps/server/src/services/search/services/types.ts b/apps/server/src/services/search/services/types.ts index 7edc3b4ae5..60d00540c6 100644 --- a/apps/server/src/services/search/services/types.ts +++ b/apps/server/src/services/search/services/types.ts @@ -21,4 +21,6 @@ export interface SearchParams { limit?: number | null; debug?: boolean; fuzzyAttributeSearch?: boolean; + /** When true, skip the two-phase fuzzy fallback and use the single-token fast path. */ + autocomplete?: boolean; } From f23a7b48429223ed21f0b944920f28b5edb6b675 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Wed, 18 Mar 2026 11:43:28 -0700 Subject: [PATCH 008/203] feat(settings): also allow for fuzzy searching to just be disabled --- apps/client/src/translations/en/translation.json | 4 ++++ .../src/widgets/type_widgets/options/other.tsx | 16 ++++++++++++++++ apps/server/src/routes/api/options.ts | 1 + apps/server/src/services/options_init.ts | 3 +++ .../server/src/services/search/search_context.ts | 7 ++++++- packages/commons/src/lib/options_interface.ts | 4 ++++ 6 files changed, 34 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index fdd6f9fb2d..f9ba8f8743 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1292,6 +1292,10 @@ "erase_excess_revision_snapshots": "Erase excess revision snapshots now", "erase_excess_revision_snapshots_prompt": "Excess revision snapshots have been erased." }, + "search": { + "title": "Search", + "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)" + }, "search_engine": { "title": "Search Engine", "custom_search_engine_info": "Custom search engine requires both a name and a URL to be set. If either of these is not set, DuckDuckGo will be used as the default search engine.", diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index e6813f8d2b..8cb99bace4 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -21,6 +21,7 @@ import TimeSelector from "./components/TimeSelector"; export default function OtherSettings() { return ( <> + {isElectron() && <> @@ -36,6 +37,21 @@ export default function OtherSettings() { ); } +function SearchSettings() { + const [ fuzzyEnabled, setFuzzyEnabled ] = useTriliumOptionBool("searchEnableFuzzyMatching"); + + return ( + + + + ); +} + function SearchEngineSettings() { const [ customSearchEngineName, setCustomSearchEngineName ] = useTriliumOption("customSearchEngineName"); const [ customSearchEngineUrl, setCustomSearchEngineUrl ] = useTriliumOption("customSearchEngineUrl"); diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index bb6ffb00d6..049a898fca 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -97,6 +97,7 @@ const ALLOWED_OPTIONS = new Set([ "layoutOrientation", "backgroundEffects", "allowedHtmlTags", + "searchEnableFuzzyMatching", "redirectBareDomain", "showLoginInShareTheme", "splitEditorOrientation", diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index a49672019d..17ea5a1f0b 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -198,6 +198,9 @@ const defaultOptions: DefaultOption[] = [ isSynced: true }, + // Search settings + { name: "searchEnableFuzzyMatching", value: "true", isSynced: true }, + // Share settings { name: "redirectBareDomain", value: "false", isSynced: true }, { name: "showLoginInShareTheme", value: "false", isSynced: true }, diff --git a/apps/server/src/services/search/search_context.ts b/apps/server/src/services/search/search_context.ts index 55d4df5d2f..79b0b6db3d 100644 --- a/apps/server/src/services/search/search_context.ts +++ b/apps/server/src/services/search/search_context.ts @@ -1,6 +1,7 @@ "use strict"; import hoistedNoteService from "../hoisted_note.js"; +import optionService from "../options.js"; import type { SearchParams } from "./services/types.js"; class SearchContext { @@ -49,7 +50,11 @@ class SearchContext { this.debugInfo = null; this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch; this.autocomplete = !!params.autocomplete; - this.enableFuzzyMatching = true; // Default to true for backward compatibility + try { + this.enableFuzzyMatching = optionService.getOptionBool("searchEnableFuzzyMatching"); + } catch { + this.enableFuzzyMatching = true; // Default to true if option not yet initialized + } this.highlightedTokens = []; this.originalQuery = ""; this.fulltextQuery = ""; // complete fulltext part diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 5582df79d2..6e36ebd7a3 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -134,6 +134,10 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Fri, 20 Mar 2026 11:26:19 -0700 Subject: [PATCH 009/203] feat(tests): implement search benchmark test... --- .../search/services/search_benchmark.spec.ts | 675 ++++++++++++++++++ 1 file changed, 675 insertions(+) create mode 100644 apps/server/src/services/search/services/search_benchmark.spec.ts diff --git a/apps/server/src/services/search/services/search_benchmark.spec.ts b/apps/server/src/services/search/services/search_benchmark.spec.ts new file mode 100644 index 0000000000..53319ff9cd --- /dev/null +++ b/apps/server/src/services/search/services/search_benchmark.spec.ts @@ -0,0 +1,675 @@ +/** + * Comprehensive search benchmark suite. + * + * Covers many scenarios: + * - Single-token, multi-token, phrase-like queries + * - Fuzzy matching enabled vs disabled + * - Autocomplete vs full search + * - Diacritics / unicode queries + * - No-match queries + * - Varying note counts (1K, 5K, 10K, 20K) + * - Warm cache vs cold cache + * + * All times are in-memory (monkeypatched getContent, no real SQL). + */ +import { describe, it, expect, afterEach } from "vitest"; +import searchService from "./search.js"; +import BNote from "../../../becca/entities/bnote.js"; +import BBranch from "../../../becca/entities/bbranch.js"; +import SearchContext from "../search_context.js"; +import becca from "../../../becca/becca.js"; +import { NoteBuilder, note } from "../../../test/becca_mocking.js"; + +// ── helpers ────────────────────────────────────────────────────────── + +function randomWord(len = 6): string { + const chars = "abcdefghijklmnopqrstuvwxyz"; + let word = ""; + for (let i = 0; i < len; i++) { + word += chars[Math.floor(Math.random() * chars.length)]; + } + return word; +} + +function generateHtmlContent(wordCount: number, includeKeywords = false, keywords?: string[]): string { + const paragraphs: string[] = []; + let wordsRemaining = wordCount; + const kws = keywords ?? []; + + while (wordsRemaining > 0) { + const paraWords = Math.min(wordsRemaining, 20 + Math.floor(Math.random() * 40)); + const words: string[] = []; + for (let i = 0; i < paraWords; i++) { + words.push(randomWord(3 + Math.floor(Math.random() * 10))); + } + if (includeKeywords && paragraphs.length === 2) { + for (let k = 0; k < kws.length; k++) { + const pos = Math.min(words.length - 1, Math.floor((words.length / (kws.length + 1)) * (k + 1))); + words[pos] = kws[k]; + } + } + paragraphs.push(`

${words.join(" ")}

`); + wordsRemaining -= paraWords; + } + + return `${paragraphs.join("\n")}`; +} + +function timed(fn: () => T): [T, number] { + const start = performance.now(); + const result = fn(); + return [result, performance.now() - start]; +} + +function avg(nums: number[]): number { + return nums.reduce((a, b) => a + b, 0) / nums.length; +} + +function min(nums: number[]): number { + return Math.min(...nums); +} + +// ── dataset builder ────────────────────────────────────────────────── + +const syntheticContent: Record = {}; + +function buildDataset(noteCount: number, opts: { + matchFraction?: number; + labelsPerNote?: number; + depth?: number; + contentWordCount?: number; + varyContentSize?: boolean; + titleKeywords?: string[]; + contentKeywords?: string[]; + /** Include notes with diacritics in titles */ + includeDiacritics?: boolean; +} = {}) { + const { + matchFraction = 0.1, + labelsPerNote = 3, + depth = 4, + contentWordCount = 300, + varyContentSize = true, + titleKeywords = ["target"], + contentKeywords = titleKeywords, + includeDiacritics = false, + } = opts; + + becca.reset(); + for (const key of Object.keys(syntheticContent)) { + delete syntheticContent[key]; + } + + const rootNote = new NoteBuilder(new BNote({ noteId: "root", title: "root", type: "text" })); + new BBranch({ + branchId: "none_root", + noteId: "root", + parentNoteId: "none", + notePosition: 10 + }); + + const containers: NoteBuilder[] = []; + let parent = rootNote; + for (let d = 0; d < depth; d++) { + const container = note(`Container_${d}_${randomWord(4)}`); + parent.child(container); + containers.push(container); + parent = container; + } + + const matchCount = Math.floor(noteCount * matchFraction); + const diacriticTitles = [ + "résumé", "naïve", "café", "über", "ñoño", "exposé", + "Ångström", "Üntersuchung", "São Paulo", "François" + ]; + + for (let i = 0; i < noteCount; i++) { + const isMatch = i < matchCount; + let title: string; + + if (includeDiacritics && i % 20 === 0) { + // Every 20th note gets a diacritics-heavy title + const dTitle = diacriticTitles[i % diacriticTitles.length]; + title = isMatch + ? `${dTitle} ${titleKeywords.join(" ")} Document ${i}` + : `${dTitle} ${randomWord(5)} Note ${i}`; + } else { + title = isMatch + ? `${randomWord(5)} ${titleKeywords.join(" ")} ${randomWord(5)} Document ${i}` + : `${randomWord(5)} ${randomWord(6)} ${randomWord(4)} Note ${i}`; + } + + const n = note(title); + + for (let l = 0; l < labelsPerNote; l++) { + const labelName = isMatch && l === 0 ? "category" : `label_${randomWord(4)}`; + const labelValue = isMatch && l === 0 ? `important ${titleKeywords[0]}` : randomWord(8); + n.label(labelName, labelValue); + } + + let noteWordCount = contentWordCount; + if (varyContentSize) { + const r = Math.random(); + if (r < 0.2) noteWordCount = Math.floor(contentWordCount * (0.2 + Math.random() * 0.3)); + else if (r < 0.7) noteWordCount = Math.floor(contentWordCount * (0.7 + Math.random() * 0.6)); + else if (r < 0.9) noteWordCount = Math.floor(contentWordCount * (1.3 + Math.random() * 0.7)); + else noteWordCount = Math.floor(contentWordCount * (2.0 + Math.random() * 1.0)); + } + + const includeContentKeyword = isMatch && contentKeywords.length > 0; + syntheticContent[n.note.noteId] = generateHtmlContent( + noteWordCount, + includeContentKeyword, + includeContentKeyword ? contentKeywords : undefined + ); + + const containerIndex = i % containers.length; + containers[containerIndex].child(n); + } + + // Monkeypatch getContent() + for (const noteObj of Object.values(becca.notes)) { + const noteId = noteObj.noteId; + if (syntheticContent[noteId]) { + (noteObj as any).getContent = () => syntheticContent[noteId]; + } else { + (noteObj as any).getContent = () => ""; + } + } + + return { rootNote, matchCount }; +} + +// ── benchmark runner ───────────────────────────────────────────────── + +interface BenchmarkResult { + query: string; + mode: string; + noteCount: number; + avgMs: number; + minMs: number; + resultCount: number; +} + +function runBenchmark( + query: string, + mode: "autocomplete" | "fullSearch", + fuzzyEnabled: boolean, + iterations = 5 +): BenchmarkResult { + const noteCount = Object.keys(becca.notes).length; + + // Warm up + if (mode === "autocomplete") { + searchService.searchNotesForAutocomplete(query, true); + } else { + const ctx = new SearchContext({ fastSearch: false }); + ctx.enableFuzzyMatching = fuzzyEnabled; + searchService.findResultsWithQuery(query, ctx); + } + + const times: number[] = []; + let resultCount = 0; + + for (let i = 0; i < iterations; i++) { + if (mode === "autocomplete") { + // For autocomplete, fuzzy is controlled by the global option + // We'll manipulate enableFuzzyMatching after construction + const [results, ms] = timed(() => { + // searchNotesForAutocomplete creates its own SearchContext internally + // so we need to test via findResultsWithQuery for fuzzy control + const ctx = new SearchContext({ + fastSearch: true, + includeHiddenNotes: true, + fuzzyAttributeSearch: true, + ignoreInternalAttributes: true, + autocomplete: true + }); + ctx.enableFuzzyMatching = fuzzyEnabled; + return searchService.findResultsWithQuery(query, ctx); + }); + times.push(ms); + resultCount = results.length; + } else { + const [results, ms] = timed(() => { + const ctx = new SearchContext({ fastSearch: false }); + ctx.enableFuzzyMatching = fuzzyEnabled; + return searchService.findResultsWithQuery(query, ctx); + }); + times.push(ms); + resultCount = results.length; + } + } + + return { + query, + mode: `${mode}${fuzzyEnabled ? "+fuzzy" : ""}`, + noteCount, + avgMs: avg(times), + minMs: min(times), + resultCount + }; +} + +function printTable(title: string, results: BenchmarkResult[]) { + console.log(`\n${"═".repeat(110)}`); + console.log(` ${title}`); + console.log(`${"═".repeat(110)}`); + console.log( + " " + + "Query".padEnd(35) + + "Mode".padEnd(22) + + "Notes".padStart(7) + + "Avg (ms)".padStart(12) + + "Min (ms)".padStart(12) + + "Results".padStart(10) + ); + console.log(` ${"─".repeat(98)}`); + for (const r of results) { + console.log( + " " + + `"${r.query}"`.padEnd(35) + + r.mode.padEnd(22) + + String(r.noteCount).padStart(7) + + r.avgMs.toFixed(1).padStart(12) + + r.minMs.toFixed(1).padStart(12) + + String(r.resultCount).padStart(10) + ); + } + console.log(`${"═".repeat(110)}\n`); +} + +// ── tests ──────────────────────────────────────────────────────────── + +describe("Comprehensive Search Benchmark", () => { + + afterEach(() => { + becca.reset(); + }); + + describe("Single-token queries", () => { + for (const noteCount of [1000, 5000, 10000, 20000]) { + it(`single token @ ${noteCount} notes — fuzzy on vs off, autocomplete vs full`, () => { + buildDataset(noteCount, { + matchFraction: 0.15, + titleKeywords: ["meeting"], + contentKeywords: ["meeting"], + contentWordCount: 300, + }); + + const results: BenchmarkResult[] = [ + runBenchmark("meeting", "autocomplete", false), + runBenchmark("meeting", "autocomplete", true), + runBenchmark("meeting", "fullSearch", false), + runBenchmark("meeting", "fullSearch", true), + ]; + + printTable(`Single Token "meeting" — ${noteCount} notes`, results); + expect(results[0].resultCount).toBeGreaterThan(0); + }); + } + }); + + describe("Multi-token queries", () => { + for (const noteCount of [1000, 5000, 10000, 20000]) { + it(`multi token @ ${noteCount} notes — fuzzy on vs off`, () => { + buildDataset(noteCount, { + matchFraction: 0.15, + titleKeywords: ["meeting", "notes", "january"], + contentKeywords: ["meeting", "notes", "january"], + contentWordCount: 400, + }); + + const results: BenchmarkResult[] = [ + // 2-token + runBenchmark("meeting notes", "autocomplete", false), + runBenchmark("meeting notes", "autocomplete", true), + runBenchmark("meeting notes", "fullSearch", false), + runBenchmark("meeting notes", "fullSearch", true), + // 3-token + runBenchmark("meeting notes january", "autocomplete", false), + runBenchmark("meeting notes january", "autocomplete", true), + runBenchmark("meeting notes january", "fullSearch", false), + runBenchmark("meeting notes january", "fullSearch", true), + ]; + + printTable(`Multi Token — ${noteCount} notes`, results); + expect(results[0].resultCount).toBeGreaterThan(0); + }); + } + }); + + describe("No-match queries (worst case — full scan, zero results)", () => { + for (const noteCount of [1000, 5000, 10000, 20000]) { + it(`no-match @ ${noteCount} notes`, () => { + buildDataset(noteCount, { + matchFraction: 0.1, + titleKeywords: ["target"], + contentKeywords: ["target"], + contentWordCount: 300, + }); + + const results: BenchmarkResult[] = [ + runBenchmark("xyznonexistent", "autocomplete", false), + runBenchmark("xyznonexistent", "autocomplete", true), + runBenchmark("xyznonexistent", "fullSearch", false), + runBenchmark("xyznonexistent", "fullSearch", true), + runBenchmark("xyzfoo xyzbar", "autocomplete", false), + runBenchmark("xyzfoo xyzbar", "autocomplete", true), + runBenchmark("xyzfoo xyzbar", "fullSearch", false), + runBenchmark("xyzfoo xyzbar", "fullSearch", true), + ]; + + printTable(`No-Match Queries — ${noteCount} notes`, results); + // All should return 0 results + for (const r of results) { + expect(r.resultCount).toBe(0); + } + }); + } + }); + + describe("Diacritics / Unicode queries", () => { + for (const noteCount of [1000, 5000, 10000]) { + it(`diacritics @ ${noteCount} notes`, () => { + buildDataset(noteCount, { + matchFraction: 0.15, + titleKeywords: ["résumé"], + contentKeywords: ["résumé"], + contentWordCount: 300, + includeDiacritics: true, + }); + + const results: BenchmarkResult[] = [ + // Exact diacritics + runBenchmark("résumé", "autocomplete", false), + runBenchmark("résumé", "autocomplete", true), + // ASCII equivalent (should still match via normalize) + runBenchmark("resume", "autocomplete", false), + runBenchmark("resume", "autocomplete", true), + // Full search + runBenchmark("résumé", "fullSearch", false), + runBenchmark("resume", "fullSearch", false), + ]; + + printTable(`Diacritics "résumé" / "resume" — ${noteCount} notes`, results); + }); + } + }); + + describe("Partial / prefix queries (simulating typing)", () => { + for (const noteCount of [5000, 10000, 20000]) { + it(`typing progression @ ${noteCount} notes`, () => { + buildDataset(noteCount, { + matchFraction: 0.15, + titleKeywords: ["documentation"], + contentKeywords: ["documentation"], + contentWordCount: 300, + }); + + const results: BenchmarkResult[] = [ + runBenchmark("d", "autocomplete", false), + runBenchmark("do", "autocomplete", false), + runBenchmark("doc", "autocomplete", false), + runBenchmark("docu", "autocomplete", false), + runBenchmark("docum", "autocomplete", false), + runBenchmark("document", "autocomplete", false), + runBenchmark("documentation", "autocomplete", false), + // Same with fuzzy + runBenchmark("d", "autocomplete", true), + runBenchmark("doc", "autocomplete", true), + runBenchmark("document", "autocomplete", true), + runBenchmark("documentation", "autocomplete", true), + ]; + + printTable(`Typing Progression "documentation" — ${noteCount} notes`, results); + }); + } + }); + + describe("Attribute-matching queries", () => { + for (const noteCount of [5000, 10000]) { + it(`attribute search @ ${noteCount} notes`, () => { + buildDataset(noteCount, { + matchFraction: 0.15, + labelsPerNote: 5, + titleKeywords: ["important"], + contentKeywords: ["important"], + contentWordCount: 200, + }); + + const results: BenchmarkResult[] = [ + // "category" is a label name on matching notes + runBenchmark("category", "autocomplete", false), + runBenchmark("category", "autocomplete", true), + runBenchmark("category", "fullSearch", false), + runBenchmark("category", "fullSearch", true), + // "important" appears in both title and label value + runBenchmark("important", "autocomplete", false), + runBenchmark("important", "autocomplete", true), + ]; + + printTable(`Attribute Matching — ${noteCount} notes`, results); + }); + } + }); + + describe("Long queries (4-5 tokens)", () => { + for (const noteCount of [5000, 10000]) { + it(`long query @ ${noteCount} notes`, () => { + buildDataset(noteCount, { + matchFraction: 0.10, + titleKeywords: ["quarterly", "budget", "review", "report"], + contentKeywords: ["quarterly", "budget", "review", "report"], + contentWordCount: 500, + }); + + const results: BenchmarkResult[] = [ + runBenchmark("quarterly", "autocomplete", false), + runBenchmark("quarterly budget", "autocomplete", false), + runBenchmark("quarterly budget review", "autocomplete", false), + runBenchmark("quarterly budget review report", "autocomplete", false), + // Same with fuzzy + runBenchmark("quarterly budget review report", "autocomplete", true), + // Full search + runBenchmark("quarterly budget review report", "fullSearch", false), + runBenchmark("quarterly budget review report", "fullSearch", true), + ]; + + printTable(`Long Queries (4 tokens) — ${noteCount} notes`, results); + }); + } + }); + + describe("Mixed scenario — realistic user session", () => { + it("simulates a user session with varied queries @ 10K notes", () => { + buildDataset(10000, { + matchFraction: 0.15, + titleKeywords: ["project", "planning"], + contentKeywords: ["project", "planning", "timeline", "budget"], + contentWordCount: 400, + varyContentSize: true, + includeDiacritics: true, + depth: 6, + }); + + const results: BenchmarkResult[] = [ + // Quick autocomplete lookups (user typing in search bar) + runBenchmark("pro", "autocomplete", false), + runBenchmark("project", "autocomplete", false), + runBenchmark("project plan", "autocomplete", false), + + // Full search (user hits Enter) + runBenchmark("project", "fullSearch", false), + runBenchmark("project planning", "fullSearch", false), + runBenchmark("project planning", "fullSearch", true), + + // Typo / near-miss with fuzzy + runBenchmark("projct", "autocomplete", false), + runBenchmark("projct", "autocomplete", true), + runBenchmark("projct planing", "fullSearch", false), + runBenchmark("projct planing", "fullSearch", true), + + // No results + runBenchmark("xyznonexistent", "autocomplete", false), + runBenchmark("xyznonexistent foo", "fullSearch", true), + + // Short common substring + runBenchmark("note", "autocomplete", false), + runBenchmark("document", "autocomplete", false), + ]; + + printTable("Realistic User Session — 10K notes", results); + }); + }); + + describe("Cache warmth impact", () => { + it("cold vs warm flat text index @ 10K notes", () => { + buildDataset(10000, { + matchFraction: 0.15, + titleKeywords: ["target"], + contentKeywords: ["target"], + contentWordCount: 300, + }); + + console.log(`\n${"═".repeat(80)}`); + console.log(" Cold vs Warm Cache — 10K notes"); + console.log(`${"═".repeat(80)}`); + + // Cold: first search after dataset build (flat text index not yet built) + becca.flatTextIndex = null; + becca.dirtyFlatTextNoteIds.clear(); + const [coldResults, coldMs] = timed(() => { + const ctx = new SearchContext({ fastSearch: true, autocomplete: true }); + ctx.enableFuzzyMatching = false; + return searchService.findResultsWithQuery("target", ctx); + }); + console.log(` Cold (index build + search): ${coldMs.toFixed(1)}ms (${coldResults.length} results)`); + + // Warm: subsequent searches reuse the index + const warmTimes: number[] = []; + for (let i = 0; i < 5; i++) { + const [, ms] = timed(() => { + const ctx = new SearchContext({ fastSearch: true, autocomplete: true }); + ctx.enableFuzzyMatching = false; + return searchService.findResultsWithQuery("target", ctx); + }); + warmTimes.push(ms); + } + console.log(` Warm (reuse index, 5 runs): avg ${avg(warmTimes).toFixed(1)}ms min ${min(warmTimes).toFixed(1)}ms`); + + // Incremental: dirty a few notes and search again + const noteIds = Object.keys(becca.notes).slice(0, 50); + for (const nid of noteIds) { + becca.dirtyNoteFlatText(nid); + } + const [, incrMs] = timed(() => { + const ctx = new SearchContext({ fastSearch: true, autocomplete: true }); + ctx.enableFuzzyMatching = false; + return searchService.findResultsWithQuery("target", ctx); + }); + console.log(` Incremental (50 dirty notes): ${incrMs.toFixed(1)}ms`); + + // Full rebuild + becca.flatTextIndex = null; + const [, rebuildMs] = timed(() => { + const ctx = new SearchContext({ fastSearch: true, autocomplete: true }); + ctx.enableFuzzyMatching = false; + return searchService.findResultsWithQuery("target", ctx); + }); + console.log(` Full rebuild (index = null): ${rebuildMs.toFixed(1)}ms`); + + console.log(`${"═".repeat(80)}\n`); + }); + }); + + describe("Fuzzy matching effectiveness comparison", () => { + it("exact vs fuzzy result quality @ 10K notes", () => { + buildDataset(10000, { + matchFraction: 0.10, + titleKeywords: ["performance"], + contentKeywords: ["performance", "optimization"], + contentWordCount: 300, + }); + + console.log(`\n${"═".repeat(90)}`); + console.log(" Fuzzy Matching Effectiveness — 10K notes"); + console.log(`${"═".repeat(90)}`); + console.log( + " " + + "Query".padEnd(30) + + "Fuzzy".padEnd(8) + + "Time (ms)".padStart(12) + + "Results".padStart(10) + + " Notes" + ); + console.log(` ${"─".repeat(70)}`); + + const queries = [ + "performance", // exact match + "performanc", // truncated + "preformance", // typo + "performence", // common misspelling + "optimization", // exact match + "optimzation", // typo + "perf optim", // abbreviated multi + ]; + + for (const query of queries) { + for (const fuzzy of [false, true]) { + const times: number[] = []; + let resultCount = 0; + for (let i = 0; i < 3; i++) { + const [results, ms] = timed(() => { + const ctx = new SearchContext({ fastSearch: true }); + ctx.enableFuzzyMatching = fuzzy; + return searchService.findResultsWithQuery(query, ctx); + }); + times.push(ms); + resultCount = results.length; + } + console.log( + " " + + `"${query}"`.padEnd(30) + + (fuzzy ? "ON" : "OFF").padEnd(8) + + avg(times).toFixed(1).padStart(12) + + String(resultCount).padStart(10) + ); + } + } + + console.log(`${"═".repeat(90)}\n`); + }); + }); + + describe("Scale comparison summary", () => { + it("summary table across all note counts", () => { + const summaryResults: BenchmarkResult[] = []; + + for (const noteCount of [1000, 5000, 10000, 20000]) { + buildDataset(noteCount, { + matchFraction: 0.15, + titleKeywords: ["meeting", "notes"], + contentKeywords: ["meeting", "notes"], + contentWordCount: 400, + varyContentSize: true, + depth: 5, + }); + + // Core scenarios + summaryResults.push(runBenchmark("meeting", "autocomplete", false)); + summaryResults.push(runBenchmark("meeting", "autocomplete", true)); + summaryResults.push(runBenchmark("meeting notes", "autocomplete", false)); + summaryResults.push(runBenchmark("meeting notes", "autocomplete", true)); + summaryResults.push(runBenchmark("meeting", "fullSearch", false)); + summaryResults.push(runBenchmark("meeting", "fullSearch", true)); + summaryResults.push(runBenchmark("meeting notes", "fullSearch", false)); + summaryResults.push(runBenchmark("meeting notes", "fullSearch", true)); + summaryResults.push(runBenchmark("xyznonexistent", "autocomplete", false)); + summaryResults.push(runBenchmark("xyznonexistent", "fullSearch", true)); + } + + printTable("Scale Comparison Summary", summaryResults); + }); + }); +}); From ac13af73c50d7ece3009129d8b560e882f0b3f7a Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 11:38:56 -0700 Subject: [PATCH 010/203] feat(search): add FTS5 migration for content search index --- .../migrations/0235__add_fts5_content_search.ts | 14 ++++++++++++++ apps/server/src/migrations/migrations.ts | 5 +++++ 2 files changed, 19 insertions(+) create mode 100644 apps/server/src/migrations/0235__add_fts5_content_search.ts diff --git a/apps/server/src/migrations/0235__add_fts5_content_search.ts b/apps/server/src/migrations/0235__add_fts5_content_search.ts new file mode 100644 index 0000000000..d0767d51b4 --- /dev/null +++ b/apps/server/src/migrations/0235__add_fts5_content_search.ts @@ -0,0 +1,14 @@ +import sql from "../services/sql.js"; +import log from "../services/log.js"; + +export default () => { + sql.execute(/*sql*/` + CREATE VIRTUAL TABLE IF NOT EXISTS note_content_fts USING fts5( + noteId UNINDEXED, + content, + tokenize='unicode61 remove_diacritics 2' + ) + `); + + log.info("Created note_content_fts table. FTS index will be populated on first search."); +}; diff --git a/apps/server/src/migrations/migrations.ts b/apps/server/src/migrations/migrations.ts index 7aca1f802b..e9f8b8d72d 100644 --- a/apps/server/src/migrations/migrations.ts +++ b/apps/server/src/migrations/migrations.ts @@ -6,6 +6,11 @@ // Migrations should be kept in descending order, so the latest migration is first. const MIGRATIONS: (SqlMigration | JsMigration)[] = [ + // Add FTS5 virtual table for full-text content search + { + version: 235, + module: async () => import("./0235__add_fts5_content_search.js") + }, // Migrate aiChat notes to code notes since LLM integration has been removed { version: 234, From dcaebeea8312f7b1175ebae9a916b89a6fee118b Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 11:39:54 -0700 Subject: [PATCH 011/203] feat(search): add FTS5 index service for content search --- .../src/services/search/fts_index.spec.ts | 13 ++ apps/server/src/services/search/fts_index.ts | 178 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 apps/server/src/services/search/fts_index.spec.ts create mode 100644 apps/server/src/services/search/fts_index.ts diff --git a/apps/server/src/services/search/fts_index.spec.ts b/apps/server/src/services/search/fts_index.spec.ts new file mode 100644 index 0000000000..983c972a0b --- /dev/null +++ b/apps/server/src/services/search/fts_index.spec.ts @@ -0,0 +1,13 @@ +import { describe, it, expect } from "vitest"; + +describe("FTS Index Service", () => { + it("should export buildIndex, updateNote, removeNote, searchContent functions", async () => { + const ftsIndex = await import("./fts_index.js"); + expect(typeof ftsIndex.default.buildIndex).toBe("function"); + expect(typeof ftsIndex.default.updateNote).toBe("function"); + expect(typeof ftsIndex.default.removeNote).toBe("function"); + expect(typeof ftsIndex.default.searchContent).toBe("function"); + expect(typeof ftsIndex.default.isIndexBuilt).toBe("function"); + expect(typeof ftsIndex.default.resetIndex).toBe("function"); + }); +}); diff --git a/apps/server/src/services/search/fts_index.ts b/apps/server/src/services/search/fts_index.ts new file mode 100644 index 0000000000..83c7616999 --- /dev/null +++ b/apps/server/src/services/search/fts_index.ts @@ -0,0 +1,178 @@ +"use strict"; + +import sql from "../sql.js"; +import log from "../log.js"; +import protectedSessionService from "../protected_session.js"; +import preprocessContent from "./expressions/note_content_fulltext_preprocessor.js"; + +interface ContentRow { + noteId: string; + type: string; + mime: string; + content: string | Buffer | null; + isProtected: number; + isDeleted: number; +} + +const MAX_CONTENT_SIZE = 2 * 1024 * 1024; + +let indexBuilt = false; + +function prepareContent(row: ContentRow): string | null { + if (!row.content) return null; + if (row.isDeleted) return null; + + let content: string | undefined; + + if (row.isProtected) { + if (!protectedSessionService.isProtectedSessionAvailable()) { + return null; + } + try { + content = protectedSessionService.decryptString(row.content as string) || undefined; + } catch { + return null; + } + } else { + content = typeof row.content === "string" ? row.content : row.content.toString(); + } + + if (!content || content.length > MAX_CONTENT_SIZE) return null; + + try { + content = preprocessContent(content, row.type, row.mime); + } catch { + return null; + } + + return content || null; +} + +function ftsTableExists(): boolean { + try { + const result = sql.getValue( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='note_content_fts'" + ); + return result > 0; + } catch { + return false; + } +} + +function buildIndex(): void { + if (!ftsTableExists()) { + log.info("FTS5 table does not exist, skipping index build."); + return; + } + + const startTime = Date.now(); + log.info("Building FTS content index..."); + + sql.execute("DELETE FROM note_content_fts"); + + const count = sql.transactional(() => { + let count = 0; + + for (const row of sql.iterateRows(` + SELECT noteId, type, mime, content, isProtected, isDeleted + FROM notes JOIN blobs USING (blobId) + WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') + AND isDeleted = 0 + AND content IS NOT NULL + AND LENGTH(content) < ${MAX_CONTENT_SIZE} + `)) { + const processedContent = prepareContent(row); + if (processedContent) { + sql.execute( + "INSERT INTO note_content_fts (noteId, content) VALUES (?, ?)", + [row.noteId, processedContent] + ); + count++; + } + } + + return count; + }); + + const elapsed = Date.now() - startTime; + log.info(`FTS content index built: ${count} notes indexed in ${elapsed}ms`); + indexBuilt = true; +} + +function updateNote(noteId: string): void { + if (!indexBuilt || !ftsTableExists()) return; + + sql.execute("DELETE FROM note_content_fts WHERE noteId = ?", [noteId]); + + const row = sql.getRowOrNull(` + SELECT noteId, type, mime, content, isProtected, isDeleted + FROM notes JOIN blobs USING (blobId) + WHERE noteId = ? + `, [noteId]); + + if (!row) return; + + const processedContent = prepareContent(row); + if (processedContent) { + sql.execute( + "INSERT INTO note_content_fts (noteId, content) VALUES (?, ?)", + [row.noteId, processedContent] + ); + } +} + +function removeNote(noteId: string): void { + if (!indexBuilt || !ftsTableExists()) return; + sql.execute("DELETE FROM note_content_fts WHERE noteId = ?", [noteId]); +} + +function searchContent(tokens: string[], operator: string = "*=*"): string[] { + if (!ftsTableExists()) return []; + + if (!indexBuilt) { + buildIndex(); + } + + const escapedTokens = tokens.map(t => { + const cleaned = t.replace(/["*^(){}:]/g, ""); + if (!cleaned) return null; + return `"${cleaned}"`; + }).filter(Boolean); + + if (escapedTokens.length === 0) return []; + + let ftsQuery: string; + if (operator === "=") { + ftsQuery = escapedTokens.join(" "); + } else { + ftsQuery = escapedTokens.join(" AND "); + } + + try { + const results = sql.getColumn( + "SELECT noteId FROM note_content_fts WHERE note_content_fts MATCH ? ORDER BY rank", + [ftsQuery] + ); + return results; + } catch (e) { + log.info(`FTS5 query failed for "${ftsQuery}": ${e}`); + return []; + } +} + +function isIndexBuilt(): boolean { + return indexBuilt; +} + +function resetIndex(): void { + indexBuilt = false; +} + +export default { + buildIndex, + updateNote, + removeNote, + searchContent, + isIndexBuilt, + resetIndex +}; From f358563c27de16781e3da9a37594520be507a44d Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 11:40:33 -0700 Subject: [PATCH 012/203] feat(search): wire FTS index updates to note content changes --- apps/server/src/services/handlers.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/server/src/services/handlers.ts b/apps/server/src/services/handlers.ts index f32bf6ddd0..89647d8d83 100644 --- a/apps/server/src/services/handlers.ts +++ b/apps/server/src/services/handlers.ts @@ -60,6 +60,17 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED } else if (entityName === "notes") { // ENTITY_DELETED won't trigger anything since all branches/attributes are already deleted at this point runAttachedRelations(entity, "runOnNoteChange", entity); + + if (entity.isDeleted) { + try { + const ftsIndex = require("./search/fts_index.js").default; + if (ftsIndex.isIndexBuilt()) { + ftsIndex.removeNote(entity.noteId); + } + } catch { + // FTS index update failure should not block note operations + } + } } }); @@ -81,6 +92,16 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => eventService.subscribe(eventService.NOTE_CONTENT_CHANGE, ({ entity }) => { runAttachedRelations(entity, "runOnNoteContentChange", entity); + + // Update FTS content index incrementally + try { + const ftsIndex = require("./search/fts_index.js").default; + if (ftsIndex.isIndexBuilt()) { + ftsIndex.updateNote(entity.noteId); + } + } catch { + // FTS index update failure should not block note saves + } }); eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => { From bc0942180e97c6af915d9d4b5f428adb86e019a0 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 11:42:20 -0700 Subject: [PATCH 013/203] feat(search): use FTS5 index in NoteContentFulltextExp with sequential fallback For operators =, !=, and *=*, the search now tries the FTS5 index first via searchViaFts(). If FTS is unavailable or fails, it falls back to the original sequential scan. The flat text attribute search is extracted into its own searchFlatTextAttributes() method and runs after both paths. --- .../expressions/note_content_fulltext.ts | 112 +++++++++++++----- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/apps/server/src/services/search/expressions/note_content_fulltext.ts b/apps/server/src/services/search/expressions/note_content_fulltext.ts index f3e0a39333..86aa40e36a 100644 --- a/apps/server/src/services/search/expressions/note_content_fulltext.ts +++ b/apps/server/src/services/search/expressions/note_content_fulltext.ts @@ -79,7 +79,17 @@ class NoteContentFulltextExp extends Expression { const resultNoteSet = new NoteSet(); - // Search through notes with content + // Try FTS5 index first for supported operators + if (this.canUseFts()) { + const ftsWorked = this.searchViaFts(inputNoteSet, resultNoteSet); + if (ftsWorked) { + this.searchFlatTextAttributes(inputNoteSet, resultNoteSet); + return resultNoteSet; + } + // FTS unavailable or failed — fall through to sequential scan + } + + // Fallback: sequential scan (original behavior) for (const row of sql.iterateRows(` SELECT noteId, type, mime, content, isProtected FROM notes JOIN blobs USING (blobId) @@ -89,43 +99,83 @@ class NoteContentFulltextExp extends Expression { this.findInText(row, inputNoteSet, resultNoteSet); } - // For exact match with flatText, also search notes WITHOUT content (they may have matching attributes) - if (this.flatText && (this.operator === "=" || this.operator === "!=")) { - for (const note of inputNoteSet.notes) { - // Skip if already found or doesn't exist - if (resultNoteSet.hasNoteId(note.noteId) || !(note.noteId in becca.notes)) { - continue; - } + this.searchFlatTextAttributes(inputNoteSet, resultNoteSet); + return resultNoteSet; + } - const noteFromBecca = becca.notes[note.noteId]; - const flatText = noteFromBecca.getFlatText(); + /** + * Whether this operator can be served by FTS5. + */ + private canUseFts(): boolean { + return ["=", "!=", "*=*"].includes(this.operator); + } - // For flatText, only check attribute values (format: #name=value or ~name=value) - // Don't match against noteId, type, mime, or title which are also in flatText - let matches = false; - const phrase = this.tokens.join(" "); - const normalizedPhrase = normalizeSearchText(phrase); - const normalizedFlatText = normalizeSearchText(flatText); + /** + * Attempts to use the FTS5 index for content search. + * Returns true if FTS was used successfully, false to fall back to sequential scan. + */ + private searchViaFts(inputNoteSet: NoteSet, resultNoteSet: NoteSet): boolean { + try { + const ftsIndex = require("../fts_index.js").default; + const matchingNoteIds = ftsIndex.searchContent(this.tokens, this.operator); - // Check if =phrase appears in flatText (indicates attribute value match) - // For single words, use word-boundary matching to avoid substring matches - if (!normalizedPhrase.includes(' ')) { - // Single word: look for =word with word boundaries - // Split by = to get attribute values, then check each value for exact word match - const parts = normalizedFlatText.split('='); - matches = parts.slice(1).some(part => this.exactWordMatch(normalizedPhrase, part)); - } else { - // Multi-word phrase: check for substring match - matches = normalizedFlatText.includes(`=${normalizedPhrase}`); - } - - if ((this.operator === "=" && matches) || (this.operator === "!=" && !matches)) { - resultNoteSet.add(noteFromBecca); + for (const noteId of matchingNoteIds) { + if (inputNoteSet.hasNoteId(noteId) && noteId in becca.notes) { + if (this.operator === "!=") { + continue; + } + resultNoteSet.add(becca.notes[noteId]); } } + + if (this.operator === "!=") { + const matchingSet = new Set(matchingNoteIds); + for (const note of inputNoteSet.notes) { + if (!matchingSet.has(note.noteId) && note.noteId in becca.notes) { + resultNoteSet.add(becca.notes[note.noteId]); + } + } + } + + return true; + } catch { + return false; + } + } + + /** + * Searches flat text attributes for = and != operators. + * Extracted from the old execute() tail. + */ + private searchFlatTextAttributes(inputNoteSet: NoteSet, resultNoteSet: NoteSet): void { + if (!this.flatText || (this.operator !== "=" && this.operator !== "!=")) { + return; } - return resultNoteSet; + for (const note of inputNoteSet.notes) { + if (resultNoteSet.hasNoteId(note.noteId) || !(note.noteId in becca.notes)) { + continue; + } + + const noteFromBecca = becca.notes[note.noteId]; + const flatText = noteFromBecca.getFlatText(); + + let matches = false; + const phrase = this.tokens.join(" "); + const normalizedPhrase = normalizeSearchText(phrase); + const normalizedFlatText = normalizeSearchText(flatText); + + if (!normalizedPhrase.includes(' ')) { + const parts = normalizedFlatText.split('='); + matches = parts.slice(1).some(part => this.exactWordMatch(normalizedPhrase, part)); + } else { + matches = normalizedFlatText.includes(`=${normalizedPhrase}`); + } + + if ((this.operator === "=" && matches) || (this.operator === "!=" && !matches)) { + resultNoteSet.add(noteFromBecca); + } + } } /** From 06fb9c0a6bf9efae5a70a41389159152d3674e3d Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 11:43:23 -0700 Subject: [PATCH 014/203] test(search): add FTS5 integration test --- apps/server/spec/fts5_search.spec.ts | 76 ++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 apps/server/spec/fts5_search.spec.ts diff --git a/apps/server/spec/fts5_search.spec.ts b/apps/server/spec/fts5_search.spec.ts new file mode 100644 index 0000000000..7d6ed7483a --- /dev/null +++ b/apps/server/spec/fts5_search.spec.ts @@ -0,0 +1,76 @@ +import { Application } from "express"; +import { beforeAll, describe, expect, it } from "vitest"; +import config from "../src/services/config.js"; + +let app: Application; + +function timed(fn: () => T): [T, number] { + const start = performance.now(); + const result = fn(); + return [result, performance.now() - start]; +} + +describe("FTS5 Content Search (integration)", () => { + beforeAll(async () => { + config.General.noAuthentication = true; + const buildApp = (await import("../src/app.js")).default; + app = await buildApp(); + }); + + it("FTS5 index builds and searches correctly", async () => { + const sql = (await import("../src/services/sql.js")).default; + const becca = (await import("../src/becca/becca.js")).default; + const ftsIndex = (await import("../src/services/search/fts_index.js")).default; + const cls = (await import("../src/services/cls.js")).default; + + await new Promise((resolve) => { + cls.init(() => { + // Check if FTS table exists (migration may not have run on test DB) + const tableExists = sql.getValue( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='note_content_fts'" + ); + + if (!tableExists) { + // Create the table for testing + sql.execute(` + CREATE VIRTUAL TABLE IF NOT EXISTS note_content_fts USING fts5( + noteId UNINDEXED, + content, + tokenize='unicode61 remove_diacritics 2' + ) + `); + } + + const noteCount = Object.keys(becca.notes).length; + console.log(`\n Notes in becca: ${noteCount}`); + + // Build the index + ftsIndex.resetIndex(); + const [, buildMs] = timed(() => ftsIndex.buildIndex()); + console.log(` FTS index build: ${buildMs.toFixed(0)}ms`); + + // Verify index has content + const indexedCount = sql.getValue("SELECT COUNT(*) FROM note_content_fts"); + console.log(` Notes indexed: ${indexedCount}`); + expect(indexedCount).toBeGreaterThanOrEqual(0); + + // If we have indexed content, test search + if (indexedCount > 0) { + const [results, searchMs] = timed(() => ftsIndex.searchContent(["note"], "*=*")); + console.log(` FTS search "note": ${searchMs.toFixed(1)}ms (${results.length} results)`); + expect(results).toBeInstanceOf(Array); + } + + // Test update and remove don't throw + expect(() => ftsIndex.updateNote("nonexistent")).not.toThrow(); + expect(() => ftsIndex.removeNote("nonexistent")).not.toThrow(); + + // Clean up + sql.execute("DELETE FROM note_content_fts"); + ftsIndex.resetIndex(); + + resolve(); + }); + }); + }); +}); From 24a01aefe2f674f0362e8e0f207b10af1669f0bd Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 11:44:19 -0700 Subject: [PATCH 015/203] feat(search): add user option to enable/disable FTS5 content index --- apps/client/src/translations/en/translation.json | 3 ++- apps/client/src/widgets/type_widgets/options/other.tsx | 7 +++++++ apps/server/src/routes/api/options.ts | 1 + apps/server/src/services/options_init.ts | 1 + .../services/search/expressions/note_content_fulltext.ts | 8 ++++++++ packages/commons/src/lib/options_interface.ts | 2 ++ 6 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index f9ba8f8743..5232fa1dc1 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1294,7 +1294,8 @@ }, "search": { "title": "Search", - "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)" + "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)", + "enable_fts5": "Use content index for faster full-text search (applies on next search)" }, "search_engine": { "title": "Search Engine", diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index 8cb99bace4..c16fffc891 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -39,6 +39,7 @@ export default function OtherSettings() { function SearchSettings() { const [ fuzzyEnabled, setFuzzyEnabled ] = useTriliumOptionBool("searchEnableFuzzyMatching"); + const [ fts5Enabled, setFts5Enabled ] = useTriliumOptionBool("searchEnableFts5"); return ( @@ -48,6 +49,12 @@ function SearchSettings() { currentValue={fuzzyEnabled} onChange={setFuzzyEnabled} /> + ); } diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 049a898fca..5491fbf31e 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -98,6 +98,7 @@ const ALLOWED_OPTIONS = new Set([ "backgroundEffects", "allowedHtmlTags", "searchEnableFuzzyMatching", + "searchEnableFts5", "redirectBareDomain", "showLoginInShareTheme", "splitEditorOrientation", diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index 17ea5a1f0b..de22bbaafd 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -200,6 +200,7 @@ const defaultOptions: DefaultOption[] = [ // Search settings { name: "searchEnableFuzzyMatching", value: "true", isSynced: true }, + { name: "searchEnableFts5", value: "true", isSynced: true }, // Share settings { name: "redirectBareDomain", value: "false", isSynced: true }, diff --git a/apps/server/src/services/search/expressions/note_content_fulltext.ts b/apps/server/src/services/search/expressions/note_content_fulltext.ts index 86aa40e36a..f2e0122d32 100644 --- a/apps/server/src/services/search/expressions/note_content_fulltext.ts +++ b/apps/server/src/services/search/expressions/note_content_fulltext.ts @@ -107,6 +107,14 @@ class NoteContentFulltextExp extends Expression { * Whether this operator can be served by FTS5. */ private canUseFts(): boolean { + try { + const optionService = require("../../options.js").default; + if (!optionService.getOptionBool("searchEnableFts5")) { + return false; + } + } catch { + // Option not available yet — allow FTS + } return ["=", "!=", "*=*"].includes(this.operator); } diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 6e36ebd7a3..555fd20a5b 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -137,6 +137,8 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Fri, 20 Mar 2026 11:56:49 -0700 Subject: [PATCH 016/203] fix(search): fix busy connection error in FTS5 index build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collect rows before inserting — iterateRows() holds an open cursor that conflicts with writes on the same connection. --- apps/server/src/services/search/fts_index.ts | 42 +++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/apps/server/src/services/search/fts_index.ts b/apps/server/src/services/search/fts_index.ts index 83c7616999..30359f7d19 100644 --- a/apps/server/src/services/search/fts_index.ts +++ b/apps/server/src/services/search/fts_index.ts @@ -70,28 +70,32 @@ function buildIndex(): void { sql.execute("DELETE FROM note_content_fts"); - const count = sql.transactional(() => { - let count = 0; + // Collect all rows first, then batch-insert in a transaction. + // iterateRows() holds an open cursor that conflicts with writes on the same connection. + const prepared: { noteId: string; content: string }[] = []; - for (const row of sql.iterateRows(` - SELECT noteId, type, mime, content, isProtected, isDeleted - FROM notes JOIN blobs USING (blobId) - WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') - AND isDeleted = 0 - AND content IS NOT NULL - AND LENGTH(content) < ${MAX_CONTENT_SIZE} - `)) { - const processedContent = prepareContent(row); - if (processedContent) { - sql.execute( - "INSERT INTO note_content_fts (noteId, content) VALUES (?, ?)", - [row.noteId, processedContent] - ); - count++; - } + for (const row of sql.iterateRows(` + SELECT noteId, type, mime, content, isProtected, isDeleted + FROM notes JOIN blobs USING (blobId) + WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') + AND isDeleted = 0 + AND content IS NOT NULL + AND LENGTH(content) < ${MAX_CONTENT_SIZE} + `)) { + const processedContent = prepareContent(row); + if (processedContent) { + prepared.push({ noteId: row.noteId, content: processedContent }); } + } - return count; + const count = sql.transactional(() => { + for (const { noteId, content } of prepared) { + sql.execute( + "INSERT INTO note_content_fts (noteId, content) VALUES (?, ?)", + [noteId, content] + ); + } + return prepared.length; }); const elapsed = Date.now() - startTime; From 87fc4e12811c01ea7d01ee9b32ceb54963c83803 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 12:00:16 -0700 Subject: [PATCH 017/203] docs(search): add FTS5 benchmark results to performance comparison Adds real SQLite benchmarks showing FTS5 is 15-33x faster for the raw content query, though end-to-end improvement is masked by JS pipeline overhead (scoring, snippets, path walking). --- docs/search-performance-benchmarks.md | 465 ++++++++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 docs/search-performance-benchmarks.md diff --git a/docs/search-performance-benchmarks.md b/docs/search-performance-benchmarks.md new file mode 100644 index 0000000000..bb7411230c --- /dev/null +++ b/docs/search-performance-benchmarks.md @@ -0,0 +1,465 @@ +# Search Performance Benchmarks: `main` vs `feat/search-perf-take1` + +> **Date:** 2026-03-20 +> **Environment:** In-memory benchmarks (monkeypatched `getContent()`, no real SQLite I/O). Both branches tested on the same machine in the same session for fair comparison. All times are avg of 5 iterations with warm caches unless noted. +> **Benchmark source:** `apps/server/src/services/search/services/search_benchmark.spec.ts` + +--- + +## Table of Contents + +- [Single-Token Autocomplete](#single-token-autocomplete) +- [Multi-Token Autocomplete](#multi-token-autocomplete) +- [No-Match Queries (worst case)](#no-match-queries-worst-case) +- [Diacritics / Unicode](#diacritics--unicode) +- [Typing Progression (keystroke simulation)](#typing-progression-keystroke-simulation) +- [Long Queries (4 tokens)](#long-queries-4-tokens) +- [Attribute Matching](#attribute-matching) +- [Fuzzy Matching Effectiveness (typos & misspellings)](#fuzzy-matching-effectiveness-typos--misspellings) +- [Cache Warmth Impact (feature branch only)](#cache-warmth-impact-feature-branch-only) +- [Realistic User Session](#realistic-user-session) +- [Scale Comparison Summary](#scale-comparison-summary) + +--- + +## Single-Token Autocomplete + +The most common case — user typing in the search bar. Query: `"meeting"`. + +### Autocomplete (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.6ms | 2.8ms | **-22%** | +| 5,000 | 11.9ms | 10.6ms | **-11%** | +| 10,000 | 27.5ms | 22.8ms | **-17%** | +| 20,000 | 53.7ms | 46.2ms | **-14%** | + +### Autocomplete (fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 2.4ms | 2.3ms | -4% | +| 5,000 | 11.7ms | 10.7ms | **-9%** | +| 10,000 | 28.9ms | 21.6ms | **-25%** | +| 20,000 | 58.6ms | 44.5ms | **-24%** | + +### Full Search (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 2.7ms | 4.3ms | +59% | +| 5,000 | 14.3ms | 10.8ms | **-24%** | +| 10,000 | 30.8ms | 26.9ms | **-13%** | +| 20,000 | 63.1ms | 56.7ms | **-10%** | + +### Full Search (fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 2.5ms | 2.4ms | -4% | +| 5,000 | 13.0ms | 11.4ms | **-12%** | +| 10,000 | 29.8ms | 25.6ms | **-14%** | +| 20,000 | 63.4ms | 54.5ms | **-14%** | + +--- + +## Multi-Token Autocomplete + +### 2-Token: `"meeting notes"` (autocomplete, fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.7ms | 3.5ms | -5% | +| 5,000 | 19.0ms | 19.3ms | +2% | +| 10,000 | 40.2ms | 40.4ms | 0% | +| 20,000 | 86.1ms | 80.7ms | **-6%** | + +### 3-Token: `"meeting notes january"` (autocomplete, fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 4.1ms | 4.3ms | +5% | +| 5,000 | 25.7ms | 24.9ms | -3% | +| 10,000 | 50.9ms | 50.5ms | -1% | +| 20,000 | 104.5ms | 107.2ms | +3% | + +### 2-Token: `"meeting notes"` (full search, fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.4ms | 3.3ms | -3% | +| 5,000 | 22.3ms | 21.9ms | -2% | +| 10,000 | 42.9ms | 40.2ms | **-6%** | +| 20,000 | 95.8ms | 88.3ms | **-8%** | + +### 3-Token: `"meeting notes january"` (full search, fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 4.4ms | 4.3ms | -2% | +| 5,000 | 26.3ms | 25.5ms | -3% | +| 10,000 | 51.7ms | 52.6ms | +2% | +| 20,000 | 113.9ms | 114.0ms | 0% | + +--- + +## No-Match Queries (worst case) + +These are the worst case — every note must be scanned with no early exit. + +### Single token: `"xyznonexistent"` (autocomplete) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 0.7ms | 0.5ms | **-29%** | +| 5,000 | 4.0ms | 3.4ms | **-15%** | +| 10,000 | 11.3ms | 7.0ms | **-38%** | +| 20,000 | 28.9ms | 19.0ms | **-34%** | + +### Single token: `"xyznonexistent"` (autocomplete, fuzzy ON) + +This is the biggest behavioral change. On `main`, autocomplete with fuzzy ON triggers the expensive two-phase search. On the feature branch, autocomplete **always skips** the fuzzy fallback phase. + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 1.7ms | 0.5ms | **-71%** | +| 5,000 | 12.8ms | 2.3ms | **-82%** | +| 10,000 | 26.4ms | 6.0ms | **-77%** | +| 20,000 | 60.4ms | 20.0ms | **-67%** | + +### Multi token: `"xyzfoo xyzbar"` (autocomplete, fuzzy ON) + +Same effect — autocomplete no longer triggers the fuzzy fallback: + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 6.5ms | 0.4ms | **-94%** | +| 5,000 | 33.9ms | 2.5ms | **-93%** | +| 10,000 | 134.5ms | 6.0ms | **-96%** | +| 20,000 | 151.8ms | 19.8ms | **-87%** | + +### Multi token: `"xyzfoo xyzbar"` (full search, fuzzy ON) + +Full search still does two-phase fuzzy on both branches, so improvement here is from the flat text index and pre-normalized attributes: + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 5.9ms | 5.8ms | -2% | +| 5,000 | 35.0ms | 33.7ms | -4% | +| 10,000 | 144.0ms | 68.8ms | **-52%** | +| 20,000 | 165.5ms | 140.6ms | **-15%** | + +--- + +## Diacritics / Unicode + +Searching `"résumé"` (with diacritics) vs `"resume"` (ASCII equivalent). Both forms find the same results thanks to diacritic normalization. + +### Autocomplete (fuzzy OFF) + +| Notes | Query | main | feature | Change | +|------:|:------|-----:|--------:|-------:| +| 1,000 | `"résumé"` | 4.1ms | 2.4ms | **-41%** | +| 1,000 | `"resume"` | 2.9ms | 2.4ms | **-17%** | +| 5,000 | `"résumé"` | 20.4ms | 15.0ms | **-26%** | +| 5,000 | `"resume"` | 18.1ms | 16.3ms | **-10%** | +| 10,000 | `"résumé"` | 40.6ms | 29.0ms | **-29%** | +| 10,000 | `"resume"` | 40.6ms | 29.5ms | **-27%** | + +--- + +## Typing Progression (keystroke simulation) + +Simulates a user typing `"documentation"` character by character. Autocomplete, fuzzy OFF. + +### 5,000 notes + +| Prefix | main | feature | Change | +|:-------|-----:|--------:|-------:| +| `"d"` | 44.7ms | 35.9ms | **-20%** | +| `"do"` | 12.9ms | 11.6ms | **-10%** | +| `"doc"` | 12.0ms | 10.2ms | **-15%** | +| `"docu"` | 10.9ms | 9.4ms | **-14%** | +| `"document"` | 9.1ms | 7.3ms | **-20%** | +| `"documentation"` | 10.3ms | 8.1ms | **-21%** | + +### 10,000 notes + +| Prefix | main | feature | Change | +|:-------|-----:|--------:|-------:| +| `"d"` | 85.4ms | 70.1ms | **-18%** | +| `"do"` | 30.0ms | 24.1ms | **-20%** | +| `"doc"` | 28.3ms | 20.8ms | **-27%** | +| `"docu"` | 24.3ms | 20.1ms | **-17%** | +| `"document"` | 19.2ms | 15.9ms | **-17%** | +| `"documentation"` | 23.0ms | 16.8ms | **-27%** | + +### 20,000 notes + +| Prefix | main | feature | Change | +|:-------|-----:|--------:|-------:| +| `"d"` | 178.3ms | 142.8ms | **-20%** | +| `"do"` | 63.7ms | 50.6ms | **-21%** | +| `"doc"` | 59.1ms | 44.0ms | **-26%** | +| `"docu"` | 59.3ms | 40.6ms | **-32%** | +| `"document"` | 45.7ms | 34.1ms | **-25%** | +| `"documentation"` | 47.4ms | 33.7ms | **-29%** | + +--- + +## Long Queries (4 tokens) + +Query: `"quarterly budget review report"` — autocomplete, fuzzy OFF. + +| Notes | Tokens | main | feature | Change | +|------:|-------:|-----:|--------:|-------:| +| 5,000 | 1 | 8.8ms | 6.5ms | **-26%** | +| 5,000 | 2 | 13.7ms | 11.0ms | **-20%** | +| 5,000 | 3 | 16.7ms | 15.1ms | **-10%** | +| 5,000 | 4 | 18.9ms | 22.3ms | +18% | +| 10,000 | 1 | 18.5ms | 15.6ms | **-16%** | +| 10,000 | 2 | 25.4ms | 24.9ms | -2% | +| 10,000 | 3 | 31.7ms | 33.3ms | +5% | +| 10,000 | 4 | 39.0ms | 40.7ms | +4% | + +--- + +## Attribute Matching + +Searching by label name (`"category"`) and label value (`"important"`). Notes have 5 labels each. + +### `"category"` (autocomplete) + +| Notes | main (fuzzy OFF) | feature (fuzzy OFF) | Change | main (fuzzy ON) | feature (fuzzy ON) | Change | +|------:|------------------:|--------------------:|-------:|----------------:|-------------------:|-------:| +| 5,000 | 12.0ms | 9.5ms | **-21%** | 34.4ms | 9.7ms | **-72%** | +| 10,000 | 26.7ms | 22.7ms | **-15%** | 77.5ms | 21.0ms | **-73%** | + +### `"important"` (autocomplete) + +| Notes | main (fuzzy OFF) | feature (fuzzy OFF) | Change | main (fuzzy ON) | feature (fuzzy ON) | Change | +|------:|------------------:|--------------------:|-------:|----------------:|-------------------:|-------:| +| 5,000 | 11.1ms | 9.2ms | **-17%** | 11.6ms | 8.8ms | **-24%** | +| 10,000 | 25.4ms | 18.7ms | **-26%** | 24.2ms | 19.4ms | **-20%** | + +--- + +## Fuzzy Matching Effectiveness (typos & misspellings) + +10K notes, keyword: `"performance"`. Shows both time and result quality. + +| Query | Fuzzy | main (time) | feature (time) | Change | main (results) | feature (results) | +|:------|:------|------------:|---------------:|-------:|---------------:|------------------:| +| `"performance"` (exact) | OFF | 26.8ms | 22.3ms | **-17%** | 1,000 | 1,000 | +| `"performance"` (exact) | ON | 18.7ms | 16.3ms | **-13%** | 1,000 | 1,000 | +| `"performanc"` (truncated) | OFF | 18.6ms | 16.4ms | **-12%** | 1,000 | 1,000 | +| `"performanc"` (truncated) | ON | 18.5ms | 15.6ms | **-16%** | 1,000 | 1,000 | +| `"preformance"` (typo) | OFF | 10.6ms | 7.9ms | **-25%** | 0 | 0 | +| `"preformance"` (typo) | ON | 55.1ms | 43.4ms | **-21%** | 1,000 | 1,000 | +| `"performence"` (misspelling) | OFF | 11.5ms | 8.8ms | **-23%** | 0 | 0 | +| `"performence"` (misspelling) | ON | 56.2ms | 48.3ms | **-14%** | 1,000 | 1,000 | +| `"optimization"` | OFF | 12.6ms | 9.9ms | **-21%** | 0 | 0 | +| `"optimization"` | ON | 37.2ms | 31.6ms | **-15%** | 0 | 0 | +| `"optimzation"` (typo) | OFF | 11.6ms | 8.1ms | **-30%** | 0 | 0 | +| `"optimzation"` (typo) | ON | 44.5ms | 31.3ms | **-30%** | 0 | 0 | +| `"perf optim"` (abbreviated) | OFF | 16.5ms | 11.8ms | **-28%** | 0 | 0 | +| `"perf optim"` (abbreviated) | ON | 74.9ms | 67.2ms | **-10%** | 0 | 0 | + +**Key insight:** Fuzzy matching is equally effective on both branches (same result counts). The feature branch is simply faster at executing it. + +--- + +## Cache Warmth Impact (feature branch only) + +This section only applies to the feature branch, which introduces a new flat text index cache in Becca. `main` does not have this cache. + +| Scenario | Time | +|:---------|------:| +| Cold (first search, builds index + search) | 61.7ms | +| Warm (reuse existing index, avg of 5 runs) | 25.6ms (avg), 19.8ms (min) | +| Incremental (50 notes dirtied, then search) | 21.1ms | +| Full rebuild (index invalidated, then search) | 20.7ms | + +The first search after startup pays a one-time index build cost (~2.4x). All subsequent searches reuse the cached index. When individual notes change, only their entries are recomputed. + +--- + +## Realistic User Session + +Simulates a typical user session at 10K notes with mixed query types and typos. + +| Query | Mode | main | feature | Change | +|:------|:-----|-----:|--------:|-------:| +| `"pro"` | autocomplete | 26.9ms | 24.6ms | **-9%** | +| `"project"` | autocomplete | 28.3ms | 24.1ms | **-15%** | +| `"project plan"` | autocomplete | 35.6ms | 35.0ms | -2% | +| `"project"` | fullSearch | 32.8ms | 30.0ms | **-9%** | +| `"project planning"` | fullSearch | 37.2ms | 36.4ms | -2% | +| `"project planning"` | fullSearch+fuzzy | 36.5ms | 35.9ms | -2% | +| `"projct"` (typo) | autocomplete | 11.4ms | 6.0ms | **-47%** | +| `"projct"` (typo) | autocomplete+fuzzy | **81.2ms** | **6.7ms** | **-92%** | +| `"projct planing"` (typo) | fullSearch | 12.5ms | 8.8ms | **-30%** | +| `"projct planing"` (typo) | fullSearch+fuzzy | 116.6ms | 113.2ms | -3% | +| `"xyznonexistent"` | autocomplete | 11.4ms | 6.7ms | **-41%** | +| `"xyznonexistent foo"` | fullSearch+fuzzy | 37.4ms | 23.2ms | **-38%** | +| `"note"` (very common) | autocomplete | **106.0ms** | **92.3ms** | **-13%** | +| `"document"` | autocomplete | 24.7ms | 20.7ms | **-16%** | + +**Biggest win:** `"projct"` autocomplete+fuzzy goes from 81.2ms to 6.7ms (**-92%**) because the feature branch skips the fuzzy fallback phase for autocomplete entirely. + +--- + +## Scale Comparison Summary + +Side-by-side comparison across all note counts for the most common query patterns. + +### `"meeting"` autocomplete (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.6ms | 2.3ms | **-36%** | +| 5,000 | 11.4ms | 12.2ms | +7% | +| 10,000 | 25.1ms | 22.9ms | **-9%** | +| 20,000 | 59.4ms | 52.3ms | **-12%** | + +### `"meeting notes"` autocomplete (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 4.0ms | 2.7ms | **-33%** | +| 5,000 | 15.9ms | 17.2ms | +8% | +| 10,000 | 36.1ms | 34.2ms | **-5%** | +| 20,000 | 71.0ms | 72.9ms | +3% | + +### `"meeting"` fullSearch (fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 2.5ms | 2.4ms | -4% | +| 5,000 | 12.1ms | 13.1ms | +8% | +| 10,000 | 27.8ms | 27.1ms | -3% | +| 20,000 | 67.2ms | 57.8ms | **-14%** | + +### `"xyznonexistent"` autocomplete (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 1.3ms | 0.5ms | **-62%** | +| 5,000 | 3.1ms | 2.5ms | **-19%** | +| 10,000 | 7.7ms | 9.4ms | +22% | +| 20,000 | 22.4ms | 16.6ms | **-26%** | + +### `"xyznonexistent"` fullSearch (fuzzy ON) — worst case path + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 2.7ms | 2.5ms | -7% | +| 5,000 | 11.2ms | 9.7ms | **-13%** | +| 10,000 | 25.4ms | 30.3ms | +19% | +| 20,000 | 68.7ms | 55.2ms | **-20%** | + +--- + +## Summary of Improvements + +### Where the feature branch clearly wins (consistent 10-30% improvement): +- **Single-token autocomplete** at all scales (10-25% faster) +- **Diacritics queries** (26-41% faster at 10K notes) +- **Typing progression** (17-32% faster per keystroke at 20K notes) +- **Fuzzy typo searches** (14-30% faster while finding same results) +- **Broad term autocomplete** (e.g., `"note"` matching 8,500 results: 13% faster) + +### Where the feature branch dramatically wins (50%+ improvement): +- **Autocomplete with fuzzy ON, no-match queries** (67-96% faster — fuzzy fallback skipped entirely) +- **Autocomplete typo queries** (e.g., `"projct"` + fuzzy: 81ms -> 7ms, **-92%**) + +### Where performance is roughly equal (within noise): +- Multi-token queries at smaller scales (1-5K notes) +- Full search with fuzzy ON when there are sufficient exact matches (fuzzy phase skipped on both branches) + +### Trade-offs: +- Some individual data points show slight regressions at 5K scale (+2-8%), likely noise from shared-machine benchmarking +- Long queries (4 tokens) at 5K notes show a small regression (+18%), but this evens out at 10K +- The new flat text index has a one-time build cost on first search (~62ms at 10K notes), amortized across all subsequent searches + +--- + +## FTS5 Content Index Benchmarks + +> These benchmarks use the **real SQLite database** with actual blob content (not monkeypatched). They test the `fastSearch=false` path that users hit when pressing Enter in search or using saved searches. This is the path that was taking **seconds** in production. + +### The Architecture + +When `fastSearch=false`, the expression tree is `OrExp([NoteFlatTextExp, NoteContentFulltextExp])`. Both expressions run: +- **NoteFlatTextExp**: In-memory scan of titles/attributes (fast — 5-25ms) +- **NoteContentFulltextExp**: Scans ALL note content from SQLite blobs (slow — the bottleneck) + +FTS5 replaces the sequential blob scan in `NoteContentFulltextExp` with an indexed FTS5 MATCH query. + +### FTS5 Query-Only Performance (isolating the content scan) + +This measures just the content search portion, stripped of the expression tree, scoring, and snippet extraction overhead. + +| Notes | FTS5 MATCH query | Sequential SQL scan | FTS5 Speedup | +|------:|-----------------:|--------------------:|-------------:| +| 1,000 | **0.2ms** | 3.6ms | **15x** | +| 5,000 | **0.5ms** | 16.0ms | **33x** | +| 10,000 | **1.1ms** | 36.4ms | **32x** | + +FTS5 is **15-33x faster** than the sequential scan for the raw content query. + +### Why Full Search Doesn't Show the Same Speedup + +When measured end-to-end through `findResultsWithQuery()` with `fastSearch=false`: + +| Notes | Query | FTS5 | Sequential | Speedup | +|------:|:------|-----:|-----------:|--------:| +| 1,000 | `"performance"` | 52.1ms | 48.3ms | 0.9x | +| 5,000 | `"performance"` | 233.4ms | 227.6ms | 1.0x | +| 10,000 | `"performance"` | 517.3ms | 515.9ms | 1.0x | +| 1,000 | `"xyznonexistent"` | 46.2ms | 57.6ms | 1.2x | +| 5,000 | `"xyznonexistent"` | 272.9ms | 229.3ms | 0.8x | +| 10,000 | `"xyznonexistent"` | 460.3ms | 468.3ms | 1.0x | + +The FTS5 query itself is 32x faster, but it's **drowned out by the rest of the pipeline**: + +| Component | Time at 10K notes | % of total | +|:----------|------------------:|-----------:| +| `NoteFlatTextExp` (in-memory scan) | ~25ms | ~5% | +| `NoteContentFulltextExp` content scan | 1-36ms | ~1-7% | +| Scoring (`computeScore` per result) | ~100-200ms | ~20-40% | +| Snippet extraction | ~50-100ms | ~10-20% | +| Highlighting | ~50ms | ~10% | +| `searchPathTowardsRoot` recursion | ~100-200ms | ~20-40% | + +The content scan (which FTS5 replaces) is only **1-7% of total search time** in this benchmark. The real bottleneck at this scale is scoring, snippet extraction, and the recursive parent-path walk — all JavaScript operations that FTS5 doesn't affect. + +### Where FTS5 Will Matter Most + +FTS5 will show significant real-world improvement when: +1. **Database is large (50K-200K+ notes)** — The sequential scan reads every blob from disk. At 200K notes with varying content sizes, the I/O cost dominates. FTS5 eliminates this entirely. +2. **Notes have large content** — The benchmark uses 300-word notes (~2KB each). Real notes can be 10KB-100KB+. The sequential scan reads and preprocesses ALL of that content; FTS5 returns noteIds without touching content blobs. +3. **Disk is slow** — These benchmarks run on fast local SSD. On slower storage (network drives, spinning disks, Docker volumes), the I/O savings from FTS5 will be dramatic. + +### FTS5 Index Build Cost + +| Notes | Build time | Notes indexed | +|------:|-----------:|--------------:| +| 1,000 | 213ms | 1,015 | +| 5,000 | 943ms | 5,015 | +| 10,000 | 2,720ms | 10,015 | + +The index builds lazily on first search and is maintained incrementally via `NOTE_CONTENT_CHANGE` events. Users using `unicode61` tokenizer (not trigram) keeps the index compact. + +### Reference: Autocomplete (fastSearch=true) — Not Affected by FTS5 + +For comparison, the in-memory autocomplete path remains fast: + +| Notes | `"performance"` | `"performance optimization"` | +|------:|-----------------:|-----------------------------:| +| 1,000 | 5.2ms | 1.4ms | +| 5,000 | 10.1ms | 3.7ms | +| 10,000 | 24.4ms | 10.4ms | + +These don't use FTS5 at all — they use the `NoteFlatTextExp` in-memory path optimized by the earlier commits in this PR. From ac231374f69d44a8c960a95998c7774aca8a80bb Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 12:07:28 -0700 Subject: [PATCH 018/203] perf(search): optimize scoring, highlighting, and tree walk - Remove redundant toLowerCase() before normalizeSearchText() in search_result.ts (normalizeSearchText already lowercases) - Pre-normalize tokens once in addScoreForStrings instead of per-chunk - Skip edit distance computation entirely when fuzzy matching is disabled - Move removeDiacritic() outside the regex while-loop in highlighting - Cache normalized parent titles per search execution in note_flat_text.ts - Use Set for token lookup in searchPathTowardsRoot (O(1) vs O(n)) - Remove redundant toLowerCase in fuzzyMatchWordWithResult (inputs from smartMatch are already normalized) --- .../search/expressions/note_flat_text.ts | 36 ++++++++++------- .../src/services/search/search_result.ts | 39 ++++++++++--------- .../src/services/search/services/search.ts | 19 +++++---- .../src/services/search/utils/text_utils.ts | 24 ++++++------ 4 files changed, 65 insertions(+), 53 deletions(-) diff --git a/apps/server/src/services/search/expressions/note_flat_text.ts b/apps/server/src/services/search/expressions/note_flat_text.ts index ef3efbf66f..b12413738e 100644 --- a/apps/server/src/services/search/expressions/note_flat_text.ts +++ b/apps/server/src/services/search/expressions/note_flat_text.ts @@ -23,6 +23,18 @@ class NoteFlatTextExp extends Expression { execute(inputNoteSet: NoteSet, executionContext: any, searchContext: SearchContext) { const resultNoteSet = new NoteSet(); + // Cache normalized titles to avoid redundant normalize+getNoteTitle calls + const titleCache = new Map(); + const getNormalizedTitle = (noteId: string, parentNoteId: string): string => { + const key = `${noteId}-${parentNoteId}`; + let cached = titleCache.get(key); + if (cached === undefined) { + cached = normalizeSearchText(beccaService.getNoteTitle(noteId, parentNoteId)); + titleCache.set(key, cached); + } + return cached; + }; + /** * @param note * @param remainingTokens - tokens still needed to be found in the path towards root @@ -38,10 +50,8 @@ class NoteFlatTextExp extends Expression { const noteId = resultPath[resultPath.length - 1]; if (!resultNoteSet.hasNoteId(noteId)) { - // we could get here from multiple paths, the first one wins because the paths - // are sorted by importance + // Snapshot takenPath since it's mutable executionContext.noteIdToNotePath[noteId] = resultPath; - resultNoteSet.add(becca.notes[noteId]); } } @@ -50,18 +60,14 @@ class NoteFlatTextExp extends Expression { } if (note.parents.length === 0 || note.noteId === "root") { - // we've reached root, but there are still remaining tokens -> this candidate note produced no result return; } const foundAttrTokens: string[] = []; for (const token of remainingTokens) { - // Add defensive checks for undefined properties - const typeMatches = note.type && note.type.includes(token); - const mimeMatches = note.mime && note.mime.includes(token); - - if (typeMatches || mimeMatches) { + if ((note.type && note.type.includes(token)) || + (note.mime && note.mime.includes(token))) { foundAttrTokens.push(token); } } @@ -75,17 +81,19 @@ class NoteFlatTextExp extends Expression { } for (const parentNote of note.parents) { - const title = normalizeSearchText(beccaService.getNoteTitle(note.noteId, parentNote.noteId)); - const foundTokens: string[] = foundAttrTokens.slice(); + const title = getNormalizedTitle(note.noteId, parentNote.noteId); + + // Use Set for O(1) lookup instead of Array.includes() which is O(n) + const foundTokenSet = new Set(foundAttrTokens); for (const token of remainingTokens) { if (this.smartMatch(title, token, searchContext)) { - foundTokens.push(token); + foundTokenSet.add(token); } } - if (foundTokens.length > 0) { - const newRemainingTokens = remainingTokens.filter((token) => !foundTokens.includes(token)); + if (foundTokenSet.size > 0) { + const newRemainingTokens = remainingTokens.filter((token) => !foundTokenSet.has(token)); searchPathTowardsRoot(parentNote, newRemainingTokens, [note.noteId, ...takenPath]); } else { diff --git a/apps/server/src/services/search/search_result.ts b/apps/server/src/services/search/search_result.ts index bf8a33524b..57e2417cf7 100644 --- a/apps/server/src/services/search/search_result.ts +++ b/apps/server/src/services/search/search_result.ts @@ -59,8 +59,9 @@ class SearchResult { this.fuzzyScore = 0; // Reset fuzzy score tracking const note = becca.notes[this.noteId]; - const normalizedQuery = normalizeSearchText(fulltextQuery.toLowerCase()); - const normalizedTitle = normalizeSearchText(note.title.toLowerCase()); + // normalizeSearchText already lowercases — no need for .toLowerCase() first + const normalizedQuery = normalizeSearchText(fulltextQuery); + const normalizedTitle = normalizeSearchText(note.title); // Note ID exact match, much higher score if (note.noteId.toLowerCase() === fulltextQuery) { @@ -91,35 +92,37 @@ class SearchResult { } addScoreForStrings(tokens: string[], str: string, factor: number, enableFuzzyMatching: boolean = true) { - const normalizedStr = normalizeSearchText(str.toLowerCase()); + // normalizeSearchText already lowercases — no need for .toLowerCase() first + const normalizedStr = normalizeSearchText(str); const chunks = normalizedStr.split(" "); + // Pre-normalize tokens once instead of per-chunk + const normalizedTokens = tokens.map(t => normalizeSearchText(t)); + let tokenScore = 0; for (const chunk of chunks) { - for (const token of tokens) { - const normalizedToken = normalizeSearchText(token.toLowerCase()); - + for (let ti = 0; ti < normalizedTokens.length; ti++) { + const normalizedToken = normalizedTokens[ti]; + if (chunk === normalizedToken) { - tokenScore += SCORE_WEIGHTS.TOKEN_EXACT_MATCH * token.length * factor; + tokenScore += SCORE_WEIGHTS.TOKEN_EXACT_MATCH * tokens[ti].length * factor; } else if (chunk.startsWith(normalizedToken)) { - tokenScore += SCORE_WEIGHTS.TOKEN_PREFIX_MATCH * token.length * factor; + tokenScore += SCORE_WEIGHTS.TOKEN_PREFIX_MATCH * tokens[ti].length * factor; } else if (chunk.includes(normalizedToken)) { - tokenScore += SCORE_WEIGHTS.TOKEN_CONTAINS_MATCH * token.length * factor; - } else { - // Try fuzzy matching for individual tokens with caps applied + tokenScore += SCORE_WEIGHTS.TOKEN_CONTAINS_MATCH * tokens[ti].length * factor; + } else if (enableFuzzyMatching && + normalizedToken.length >= FUZZY_SEARCH_CONFIG.MIN_FUZZY_TOKEN_LENGTH && + this.fuzzyScore < SCORE_WEIGHTS.MAX_TOTAL_FUZZY_SCORE) { + // Only compute edit distance when fuzzy matching is enabled const editDistance = calculateOptimizedEditDistance(chunk, normalizedToken, FUZZY_SEARCH_CONFIG.MAX_EDIT_DISTANCE); - if (editDistance <= FUZZY_SEARCH_CONFIG.MAX_EDIT_DISTANCE && - normalizedToken.length >= FUZZY_SEARCH_CONFIG.MIN_FUZZY_TOKEN_LENGTH && - this.fuzzyScore < SCORE_WEIGHTS.MAX_TOTAL_FUZZY_SCORE) { - + if (editDistance <= FUZZY_SEARCH_CONFIG.MAX_EDIT_DISTANCE) { const fuzzyWeight = SCORE_WEIGHTS.TOKEN_FUZZY_MATCH * (1 - editDistance / FUZZY_SEARCH_CONFIG.MAX_EDIT_DISTANCE); - // Apply caps: limit token length multiplier and per-token contribution - const cappedTokenLength = Math.min(token.length, SCORE_WEIGHTS.MAX_FUZZY_TOKEN_LENGTH_MULTIPLIER); + const cappedTokenLength = Math.min(tokens[ti].length, SCORE_WEIGHTS.MAX_FUZZY_TOKEN_LENGTH_MULTIPLIER); const fuzzyTokenScore = Math.min( fuzzyWeight * cappedTokenLength * factor, SCORE_WEIGHTS.MAX_FUZZY_SCORE_PER_TOKEN ); - + tokenScore += fuzzyTokenScore; this.fuzzyScore += fuzzyTokenScore; } diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index b533c185fe..49eb6d0d71 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -722,37 +722,40 @@ function highlightSearchResults(searchResults: SearchResult[], highlightedTokens } for (const result of searchResults) { - // Reset token - const tokenRegex = new RegExp(escapeRegExp(token), "gi"); let match; // Highlight in note path title if (result.highlightedNotePathTitle) { const titleRegex = new RegExp(escapeRegExp(token), "gi"); - while ((match = titleRegex.exec(removeDiacritic(result.highlightedNotePathTitle))) !== null) { + // Compute diacritic-free version ONCE before the loop, not on every iteration + let titleNoDiacritics = removeDiacritic(result.highlightedNotePathTitle); + while ((match = titleRegex.exec(titleNoDiacritics)) !== null) { result.highlightedNotePathTitle = wrapText(result.highlightedNotePathTitle, match.index, token.length, "{", "}"); - // 2 characters are added, so we need to adjust the index + // 2 characters are added, so we need to adjust the index and re-derive titleRegex.lastIndex += 2; + titleNoDiacritics = removeDiacritic(result.highlightedNotePathTitle); } } // Highlight in content snippet if (result.highlightedContentSnippet) { const contentRegex = new RegExp(escapeRegExp(token), "gi"); - while ((match = contentRegex.exec(removeDiacritic(result.highlightedContentSnippet))) !== null) { + let contentNoDiacritics = removeDiacritic(result.highlightedContentSnippet); + while ((match = contentRegex.exec(contentNoDiacritics)) !== null) { result.highlightedContentSnippet = wrapText(result.highlightedContentSnippet, match.index, token.length, "{", "}"); - // 2 characters are added, so we need to adjust the index contentRegex.lastIndex += 2; + contentNoDiacritics = removeDiacritic(result.highlightedContentSnippet); } } // Highlight in attribute snippet if (result.highlightedAttributeSnippet) { const attributeRegex = new RegExp(escapeRegExp(token), "gi"); - while ((match = attributeRegex.exec(removeDiacritic(result.highlightedAttributeSnippet))) !== null) { + let attrNoDiacritics = removeDiacritic(result.highlightedAttributeSnippet); + while ((match = attributeRegex.exec(attrNoDiacritics)) !== null) { result.highlightedAttributeSnippet = wrapText(result.highlightedAttributeSnippet, match.index, token.length, "{", "}"); - // 2 characters are added, so we need to adjust the index attributeRegex.lastIndex += 2; + attrNoDiacritics = removeDiacritic(result.highlightedAttributeSnippet); } } } diff --git a/apps/server/src/services/search/utils/text_utils.ts b/apps/server/src/services/search/utils/text_utils.ts index 9274241cbc..94df2f3dd1 100644 --- a/apps/server/src/services/search/utils/text_utils.ts +++ b/apps/server/src/services/search/utils/text_utils.ts @@ -275,21 +275,19 @@ export function fuzzyMatchWordWithResult(token: string, text: string, maxDistanc } try { - // Normalize both strings for comparison - const normalizedToken = token.toLowerCase(); - const normalizedText = text.toLowerCase(); - + // Inputs from smartMatch() are already normalized (lowercased, diacritics removed). + // Skip redundant toLowerCase() — just use the inputs directly. + // Exact match check first (most common case) - if (normalizedText.includes(normalizedToken)) { - // Find the exact match in the original text to preserve case - const exactMatch = text.match(new RegExp(escapeRegExp(token), 'i')); - return exactMatch ? exactMatch[0] : token; + if (text.includes(token)) { + return token; } - - // For fuzzy matching, we need to check individual words in the text - // Split the text into words and check each word against the token - const words = normalizedText.split(/\s+/).filter(word => word.length > 0); - const originalWords = text.split(/\s+/).filter(word => word.length > 0); + + // For fuzzy matching, we need to check individual words in the text. + // Split into words and check each word against the token. + // Inputs are already normalized so we don't need a separate originalWords array. + const words = text.split(/\s+/); + const originalWords = words; for (let i = 0; i < words.length; i++) { const word = words[i]; From 48dd93b94b87e717bbcd0adb6d3d1b993b909015 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Fri, 20 Mar 2026 12:16:40 -0700 Subject: [PATCH 019/203] revert: remove FTS5 content search (no measured end-to-end improvement) FTS5 query was 32x faster in isolation, but the content scan is only 1-7% of total search time. The JS pipeline (scoring, snippets, highlighting, tree walk) dominates. The in-memory optimizations in this PR provide the real gains. Removes: migration, fts_index service, event wiring, UI option, integration test. Keeps all in-memory performance optimizations. --- .../src/translations/en/translation.json | 3 +- .../widgets/type_widgets/options/other.tsx | 7 - apps/server/spec/fts5_search.spec.ts | 76 -------- .../0235__add_fts5_content_search.ts | 14 -- apps/server/src/migrations/migrations.ts | 5 - apps/server/src/routes/api/options.ts | 1 - apps/server/src/services/handlers.ts | 21 -- apps/server/src/services/options_init.ts | 1 - .../expressions/note_content_fulltext.ts | 132 ++++--------- .../src/services/search/fts_index.spec.ts | 13 -- apps/server/src/services/search/fts_index.ts | 182 ------------------ docs/search-performance-benchmarks.md | 81 -------- packages/commons/src/lib/options_interface.ts | 2 - 13 files changed, 38 insertions(+), 500 deletions(-) delete mode 100644 apps/server/spec/fts5_search.spec.ts delete mode 100644 apps/server/src/migrations/0235__add_fts5_content_search.ts delete mode 100644 apps/server/src/services/search/fts_index.spec.ts delete mode 100644 apps/server/src/services/search/fts_index.ts diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 5232fa1dc1..f9ba8f8743 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1294,8 +1294,7 @@ }, "search": { "title": "Search", - "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)", - "enable_fts5": "Use content index for faster full-text search (applies on next search)" + "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)" }, "search_engine": { "title": "Search Engine", diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index c16fffc891..8cb99bace4 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -39,7 +39,6 @@ export default function OtherSettings() { function SearchSettings() { const [ fuzzyEnabled, setFuzzyEnabled ] = useTriliumOptionBool("searchEnableFuzzyMatching"); - const [ fts5Enabled, setFts5Enabled ] = useTriliumOptionBool("searchEnableFts5"); return ( @@ -49,12 +48,6 @@ function SearchSettings() { currentValue={fuzzyEnabled} onChange={setFuzzyEnabled} /> - ); } diff --git a/apps/server/spec/fts5_search.spec.ts b/apps/server/spec/fts5_search.spec.ts deleted file mode 100644 index 7d6ed7483a..0000000000 --- a/apps/server/spec/fts5_search.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Application } from "express"; -import { beforeAll, describe, expect, it } from "vitest"; -import config from "../src/services/config.js"; - -let app: Application; - -function timed(fn: () => T): [T, number] { - const start = performance.now(); - const result = fn(); - return [result, performance.now() - start]; -} - -describe("FTS5 Content Search (integration)", () => { - beforeAll(async () => { - config.General.noAuthentication = true; - const buildApp = (await import("../src/app.js")).default; - app = await buildApp(); - }); - - it("FTS5 index builds and searches correctly", async () => { - const sql = (await import("../src/services/sql.js")).default; - const becca = (await import("../src/becca/becca.js")).default; - const ftsIndex = (await import("../src/services/search/fts_index.js")).default; - const cls = (await import("../src/services/cls.js")).default; - - await new Promise((resolve) => { - cls.init(() => { - // Check if FTS table exists (migration may not have run on test DB) - const tableExists = sql.getValue( - "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='note_content_fts'" - ); - - if (!tableExists) { - // Create the table for testing - sql.execute(` - CREATE VIRTUAL TABLE IF NOT EXISTS note_content_fts USING fts5( - noteId UNINDEXED, - content, - tokenize='unicode61 remove_diacritics 2' - ) - `); - } - - const noteCount = Object.keys(becca.notes).length; - console.log(`\n Notes in becca: ${noteCount}`); - - // Build the index - ftsIndex.resetIndex(); - const [, buildMs] = timed(() => ftsIndex.buildIndex()); - console.log(` FTS index build: ${buildMs.toFixed(0)}ms`); - - // Verify index has content - const indexedCount = sql.getValue("SELECT COUNT(*) FROM note_content_fts"); - console.log(` Notes indexed: ${indexedCount}`); - expect(indexedCount).toBeGreaterThanOrEqual(0); - - // If we have indexed content, test search - if (indexedCount > 0) { - const [results, searchMs] = timed(() => ftsIndex.searchContent(["note"], "*=*")); - console.log(` FTS search "note": ${searchMs.toFixed(1)}ms (${results.length} results)`); - expect(results).toBeInstanceOf(Array); - } - - // Test update and remove don't throw - expect(() => ftsIndex.updateNote("nonexistent")).not.toThrow(); - expect(() => ftsIndex.removeNote("nonexistent")).not.toThrow(); - - // Clean up - sql.execute("DELETE FROM note_content_fts"); - ftsIndex.resetIndex(); - - resolve(); - }); - }); - }); -}); diff --git a/apps/server/src/migrations/0235__add_fts5_content_search.ts b/apps/server/src/migrations/0235__add_fts5_content_search.ts deleted file mode 100644 index d0767d51b4..0000000000 --- a/apps/server/src/migrations/0235__add_fts5_content_search.ts +++ /dev/null @@ -1,14 +0,0 @@ -import sql from "../services/sql.js"; -import log from "../services/log.js"; - -export default () => { - sql.execute(/*sql*/` - CREATE VIRTUAL TABLE IF NOT EXISTS note_content_fts USING fts5( - noteId UNINDEXED, - content, - tokenize='unicode61 remove_diacritics 2' - ) - `); - - log.info("Created note_content_fts table. FTS index will be populated on first search."); -}; diff --git a/apps/server/src/migrations/migrations.ts b/apps/server/src/migrations/migrations.ts index e9f8b8d72d..7aca1f802b 100644 --- a/apps/server/src/migrations/migrations.ts +++ b/apps/server/src/migrations/migrations.ts @@ -6,11 +6,6 @@ // Migrations should be kept in descending order, so the latest migration is first. const MIGRATIONS: (SqlMigration | JsMigration)[] = [ - // Add FTS5 virtual table for full-text content search - { - version: 235, - module: async () => import("./0235__add_fts5_content_search.js") - }, // Migrate aiChat notes to code notes since LLM integration has been removed { version: 234, diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 5491fbf31e..049a898fca 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -98,7 +98,6 @@ const ALLOWED_OPTIONS = new Set([ "backgroundEffects", "allowedHtmlTags", "searchEnableFuzzyMatching", - "searchEnableFts5", "redirectBareDomain", "showLoginInShareTheme", "splitEditorOrientation", diff --git a/apps/server/src/services/handlers.ts b/apps/server/src/services/handlers.ts index 89647d8d83..f32bf6ddd0 100644 --- a/apps/server/src/services/handlers.ts +++ b/apps/server/src/services/handlers.ts @@ -60,17 +60,6 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED } else if (entityName === "notes") { // ENTITY_DELETED won't trigger anything since all branches/attributes are already deleted at this point runAttachedRelations(entity, "runOnNoteChange", entity); - - if (entity.isDeleted) { - try { - const ftsIndex = require("./search/fts_index.js").default; - if (ftsIndex.isIndexBuilt()) { - ftsIndex.removeNote(entity.noteId); - } - } catch { - // FTS index update failure should not block note operations - } - } } }); @@ -92,16 +81,6 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => eventService.subscribe(eventService.NOTE_CONTENT_CHANGE, ({ entity }) => { runAttachedRelations(entity, "runOnNoteContentChange", entity); - - // Update FTS content index incrementally - try { - const ftsIndex = require("./search/fts_index.js").default; - if (ftsIndex.isIndexBuilt()) { - ftsIndex.updateNote(entity.noteId); - } - } catch { - // FTS index update failure should not block note saves - } }); eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => { diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index de22bbaafd..17ea5a1f0b 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -200,7 +200,6 @@ const defaultOptions: DefaultOption[] = [ // Search settings { name: "searchEnableFuzzyMatching", value: "true", isSynced: true }, - { name: "searchEnableFts5", value: "true", isSynced: true }, // Share settings { name: "redirectBareDomain", value: "false", isSynced: true }, diff --git a/apps/server/src/services/search/expressions/note_content_fulltext.ts b/apps/server/src/services/search/expressions/note_content_fulltext.ts index f2e0122d32..f3e0a39333 100644 --- a/apps/server/src/services/search/expressions/note_content_fulltext.ts +++ b/apps/server/src/services/search/expressions/note_content_fulltext.ts @@ -79,17 +79,7 @@ class NoteContentFulltextExp extends Expression { const resultNoteSet = new NoteSet(); - // Try FTS5 index first for supported operators - if (this.canUseFts()) { - const ftsWorked = this.searchViaFts(inputNoteSet, resultNoteSet); - if (ftsWorked) { - this.searchFlatTextAttributes(inputNoteSet, resultNoteSet); - return resultNoteSet; - } - // FTS unavailable or failed — fall through to sequential scan - } - - // Fallback: sequential scan (original behavior) + // Search through notes with content for (const row of sql.iterateRows(` SELECT noteId, type, mime, content, isProtected FROM notes JOIN blobs USING (blobId) @@ -99,93 +89,45 @@ class NoteContentFulltextExp extends Expression { this.findInText(row, inputNoteSet, resultNoteSet); } - this.searchFlatTextAttributes(inputNoteSet, resultNoteSet); + // For exact match with flatText, also search notes WITHOUT content (they may have matching attributes) + if (this.flatText && (this.operator === "=" || this.operator === "!=")) { + for (const note of inputNoteSet.notes) { + // Skip if already found or doesn't exist + if (resultNoteSet.hasNoteId(note.noteId) || !(note.noteId in becca.notes)) { + continue; + } + + const noteFromBecca = becca.notes[note.noteId]; + const flatText = noteFromBecca.getFlatText(); + + // For flatText, only check attribute values (format: #name=value or ~name=value) + // Don't match against noteId, type, mime, or title which are also in flatText + let matches = false; + const phrase = this.tokens.join(" "); + const normalizedPhrase = normalizeSearchText(phrase); + const normalizedFlatText = normalizeSearchText(flatText); + + // Check if =phrase appears in flatText (indicates attribute value match) + // For single words, use word-boundary matching to avoid substring matches + if (!normalizedPhrase.includes(' ')) { + // Single word: look for =word with word boundaries + // Split by = to get attribute values, then check each value for exact word match + const parts = normalizedFlatText.split('='); + matches = parts.slice(1).some(part => this.exactWordMatch(normalizedPhrase, part)); + } else { + // Multi-word phrase: check for substring match + matches = normalizedFlatText.includes(`=${normalizedPhrase}`); + } + + if ((this.operator === "=" && matches) || (this.operator === "!=" && !matches)) { + resultNoteSet.add(noteFromBecca); + } + } + } + return resultNoteSet; } - /** - * Whether this operator can be served by FTS5. - */ - private canUseFts(): boolean { - try { - const optionService = require("../../options.js").default; - if (!optionService.getOptionBool("searchEnableFts5")) { - return false; - } - } catch { - // Option not available yet — allow FTS - } - return ["=", "!=", "*=*"].includes(this.operator); - } - - /** - * Attempts to use the FTS5 index for content search. - * Returns true if FTS was used successfully, false to fall back to sequential scan. - */ - private searchViaFts(inputNoteSet: NoteSet, resultNoteSet: NoteSet): boolean { - try { - const ftsIndex = require("../fts_index.js").default; - const matchingNoteIds = ftsIndex.searchContent(this.tokens, this.operator); - - for (const noteId of matchingNoteIds) { - if (inputNoteSet.hasNoteId(noteId) && noteId in becca.notes) { - if (this.operator === "!=") { - continue; - } - resultNoteSet.add(becca.notes[noteId]); - } - } - - if (this.operator === "!=") { - const matchingSet = new Set(matchingNoteIds); - for (const note of inputNoteSet.notes) { - if (!matchingSet.has(note.noteId) && note.noteId in becca.notes) { - resultNoteSet.add(becca.notes[note.noteId]); - } - } - } - - return true; - } catch { - return false; - } - } - - /** - * Searches flat text attributes for = and != operators. - * Extracted from the old execute() tail. - */ - private searchFlatTextAttributes(inputNoteSet: NoteSet, resultNoteSet: NoteSet): void { - if (!this.flatText || (this.operator !== "=" && this.operator !== "!=")) { - return; - } - - for (const note of inputNoteSet.notes) { - if (resultNoteSet.hasNoteId(note.noteId) || !(note.noteId in becca.notes)) { - continue; - } - - const noteFromBecca = becca.notes[note.noteId]; - const flatText = noteFromBecca.getFlatText(); - - let matches = false; - const phrase = this.tokens.join(" "); - const normalizedPhrase = normalizeSearchText(phrase); - const normalizedFlatText = normalizeSearchText(flatText); - - if (!normalizedPhrase.includes(' ')) { - const parts = normalizedFlatText.split('='); - matches = parts.slice(1).some(part => this.exactWordMatch(normalizedPhrase, part)); - } else { - matches = normalizedFlatText.includes(`=${normalizedPhrase}`); - } - - if ((this.operator === "=" && matches) || (this.operator === "!=" && !matches)) { - resultNoteSet.add(noteFromBecca); - } - } - } - /** * Helper method to check if a single word appears as an exact match in text * @param wordToFind - The word to search for (should be normalized) diff --git a/apps/server/src/services/search/fts_index.spec.ts b/apps/server/src/services/search/fts_index.spec.ts deleted file mode 100644 index 983c972a0b..0000000000 --- a/apps/server/src/services/search/fts_index.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe("FTS Index Service", () => { - it("should export buildIndex, updateNote, removeNote, searchContent functions", async () => { - const ftsIndex = await import("./fts_index.js"); - expect(typeof ftsIndex.default.buildIndex).toBe("function"); - expect(typeof ftsIndex.default.updateNote).toBe("function"); - expect(typeof ftsIndex.default.removeNote).toBe("function"); - expect(typeof ftsIndex.default.searchContent).toBe("function"); - expect(typeof ftsIndex.default.isIndexBuilt).toBe("function"); - expect(typeof ftsIndex.default.resetIndex).toBe("function"); - }); -}); diff --git a/apps/server/src/services/search/fts_index.ts b/apps/server/src/services/search/fts_index.ts deleted file mode 100644 index 30359f7d19..0000000000 --- a/apps/server/src/services/search/fts_index.ts +++ /dev/null @@ -1,182 +0,0 @@ -"use strict"; - -import sql from "../sql.js"; -import log from "../log.js"; -import protectedSessionService from "../protected_session.js"; -import preprocessContent from "./expressions/note_content_fulltext_preprocessor.js"; - -interface ContentRow { - noteId: string; - type: string; - mime: string; - content: string | Buffer | null; - isProtected: number; - isDeleted: number; -} - -const MAX_CONTENT_SIZE = 2 * 1024 * 1024; - -let indexBuilt = false; - -function prepareContent(row: ContentRow): string | null { - if (!row.content) return null; - if (row.isDeleted) return null; - - let content: string | undefined; - - if (row.isProtected) { - if (!protectedSessionService.isProtectedSessionAvailable()) { - return null; - } - try { - content = protectedSessionService.decryptString(row.content as string) || undefined; - } catch { - return null; - } - } else { - content = typeof row.content === "string" ? row.content : row.content.toString(); - } - - if (!content || content.length > MAX_CONTENT_SIZE) return null; - - try { - content = preprocessContent(content, row.type, row.mime); - } catch { - return null; - } - - return content || null; -} - -function ftsTableExists(): boolean { - try { - const result = sql.getValue( - "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='note_content_fts'" - ); - return result > 0; - } catch { - return false; - } -} - -function buildIndex(): void { - if (!ftsTableExists()) { - log.info("FTS5 table does not exist, skipping index build."); - return; - } - - const startTime = Date.now(); - log.info("Building FTS content index..."); - - sql.execute("DELETE FROM note_content_fts"); - - // Collect all rows first, then batch-insert in a transaction. - // iterateRows() holds an open cursor that conflicts with writes on the same connection. - const prepared: { noteId: string; content: string }[] = []; - - for (const row of sql.iterateRows(` - SELECT noteId, type, mime, content, isProtected, isDeleted - FROM notes JOIN blobs USING (blobId) - WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') - AND isDeleted = 0 - AND content IS NOT NULL - AND LENGTH(content) < ${MAX_CONTENT_SIZE} - `)) { - const processedContent = prepareContent(row); - if (processedContent) { - prepared.push({ noteId: row.noteId, content: processedContent }); - } - } - - const count = sql.transactional(() => { - for (const { noteId, content } of prepared) { - sql.execute( - "INSERT INTO note_content_fts (noteId, content) VALUES (?, ?)", - [noteId, content] - ); - } - return prepared.length; - }); - - const elapsed = Date.now() - startTime; - log.info(`FTS content index built: ${count} notes indexed in ${elapsed}ms`); - indexBuilt = true; -} - -function updateNote(noteId: string): void { - if (!indexBuilt || !ftsTableExists()) return; - - sql.execute("DELETE FROM note_content_fts WHERE noteId = ?", [noteId]); - - const row = sql.getRowOrNull(` - SELECT noteId, type, mime, content, isProtected, isDeleted - FROM notes JOIN blobs USING (blobId) - WHERE noteId = ? - `, [noteId]); - - if (!row) return; - - const processedContent = prepareContent(row); - if (processedContent) { - sql.execute( - "INSERT INTO note_content_fts (noteId, content) VALUES (?, ?)", - [row.noteId, processedContent] - ); - } -} - -function removeNote(noteId: string): void { - if (!indexBuilt || !ftsTableExists()) return; - sql.execute("DELETE FROM note_content_fts WHERE noteId = ?", [noteId]); -} - -function searchContent(tokens: string[], operator: string = "*=*"): string[] { - if (!ftsTableExists()) return []; - - if (!indexBuilt) { - buildIndex(); - } - - const escapedTokens = tokens.map(t => { - const cleaned = t.replace(/["*^(){}:]/g, ""); - if (!cleaned) return null; - return `"${cleaned}"`; - }).filter(Boolean); - - if (escapedTokens.length === 0) return []; - - let ftsQuery: string; - if (operator === "=") { - ftsQuery = escapedTokens.join(" "); - } else { - ftsQuery = escapedTokens.join(" AND "); - } - - try { - const results = sql.getColumn( - "SELECT noteId FROM note_content_fts WHERE note_content_fts MATCH ? ORDER BY rank", - [ftsQuery] - ); - return results; - } catch (e) { - log.info(`FTS5 query failed for "${ftsQuery}": ${e}`); - return []; - } -} - -function isIndexBuilt(): boolean { - return indexBuilt; -} - -function resetIndex(): void { - indexBuilt = false; -} - -export default { - buildIndex, - updateNote, - removeNote, - searchContent, - isIndexBuilt, - resetIndex -}; diff --git a/docs/search-performance-benchmarks.md b/docs/search-performance-benchmarks.md index bb7411230c..614abfb917 100644 --- a/docs/search-performance-benchmarks.md +++ b/docs/search-performance-benchmarks.md @@ -382,84 +382,3 @@ Side-by-side comparison across all note counts for the most common query pattern - Some individual data points show slight regressions at 5K scale (+2-8%), likely noise from shared-machine benchmarking - Long queries (4 tokens) at 5K notes show a small regression (+18%), but this evens out at 10K - The new flat text index has a one-time build cost on first search (~62ms at 10K notes), amortized across all subsequent searches - ---- - -## FTS5 Content Index Benchmarks - -> These benchmarks use the **real SQLite database** with actual blob content (not monkeypatched). They test the `fastSearch=false` path that users hit when pressing Enter in search or using saved searches. This is the path that was taking **seconds** in production. - -### The Architecture - -When `fastSearch=false`, the expression tree is `OrExp([NoteFlatTextExp, NoteContentFulltextExp])`. Both expressions run: -- **NoteFlatTextExp**: In-memory scan of titles/attributes (fast — 5-25ms) -- **NoteContentFulltextExp**: Scans ALL note content from SQLite blobs (slow — the bottleneck) - -FTS5 replaces the sequential blob scan in `NoteContentFulltextExp` with an indexed FTS5 MATCH query. - -### FTS5 Query-Only Performance (isolating the content scan) - -This measures just the content search portion, stripped of the expression tree, scoring, and snippet extraction overhead. - -| Notes | FTS5 MATCH query | Sequential SQL scan | FTS5 Speedup | -|------:|-----------------:|--------------------:|-------------:| -| 1,000 | **0.2ms** | 3.6ms | **15x** | -| 5,000 | **0.5ms** | 16.0ms | **33x** | -| 10,000 | **1.1ms** | 36.4ms | **32x** | - -FTS5 is **15-33x faster** than the sequential scan for the raw content query. - -### Why Full Search Doesn't Show the Same Speedup - -When measured end-to-end through `findResultsWithQuery()` with `fastSearch=false`: - -| Notes | Query | FTS5 | Sequential | Speedup | -|------:|:------|-----:|-----------:|--------:| -| 1,000 | `"performance"` | 52.1ms | 48.3ms | 0.9x | -| 5,000 | `"performance"` | 233.4ms | 227.6ms | 1.0x | -| 10,000 | `"performance"` | 517.3ms | 515.9ms | 1.0x | -| 1,000 | `"xyznonexistent"` | 46.2ms | 57.6ms | 1.2x | -| 5,000 | `"xyznonexistent"` | 272.9ms | 229.3ms | 0.8x | -| 10,000 | `"xyznonexistent"` | 460.3ms | 468.3ms | 1.0x | - -The FTS5 query itself is 32x faster, but it's **drowned out by the rest of the pipeline**: - -| Component | Time at 10K notes | % of total | -|:----------|------------------:|-----------:| -| `NoteFlatTextExp` (in-memory scan) | ~25ms | ~5% | -| `NoteContentFulltextExp` content scan | 1-36ms | ~1-7% | -| Scoring (`computeScore` per result) | ~100-200ms | ~20-40% | -| Snippet extraction | ~50-100ms | ~10-20% | -| Highlighting | ~50ms | ~10% | -| `searchPathTowardsRoot` recursion | ~100-200ms | ~20-40% | - -The content scan (which FTS5 replaces) is only **1-7% of total search time** in this benchmark. The real bottleneck at this scale is scoring, snippet extraction, and the recursive parent-path walk — all JavaScript operations that FTS5 doesn't affect. - -### Where FTS5 Will Matter Most - -FTS5 will show significant real-world improvement when: -1. **Database is large (50K-200K+ notes)** — The sequential scan reads every blob from disk. At 200K notes with varying content sizes, the I/O cost dominates. FTS5 eliminates this entirely. -2. **Notes have large content** — The benchmark uses 300-word notes (~2KB each). Real notes can be 10KB-100KB+. The sequential scan reads and preprocesses ALL of that content; FTS5 returns noteIds without touching content blobs. -3. **Disk is slow** — These benchmarks run on fast local SSD. On slower storage (network drives, spinning disks, Docker volumes), the I/O savings from FTS5 will be dramatic. - -### FTS5 Index Build Cost - -| Notes | Build time | Notes indexed | -|------:|-----------:|--------------:| -| 1,000 | 213ms | 1,015 | -| 5,000 | 943ms | 5,015 | -| 10,000 | 2,720ms | 10,015 | - -The index builds lazily on first search and is maintained incrementally via `NOTE_CONTENT_CHANGE` events. Users using `unicode61` tokenizer (not trigram) keeps the index compact. - -### Reference: Autocomplete (fastSearch=true) — Not Affected by FTS5 - -For comparison, the in-memory autocomplete path remains fast: - -| Notes | `"performance"` | `"performance optimization"` | -|------:|-----------------:|-----------------------------:| -| 1,000 | 5.2ms | 1.4ms | -| 5,000 | 10.1ms | 3.7ms | -| 10,000 | 24.4ms | 10.4ms | - -These don't use FTS5 at all — they use the `NoteFlatTextExp` in-memory path optimized by the earlier commits in this PR. diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 555fd20a5b..6e36ebd7a3 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -137,8 +137,6 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Sat, 21 Mar 2026 09:17:27 -0700 Subject: [PATCH 020/203] fix(search): restore toLowerCase in fuzzyMatchWordWithResult The function has multiple callers (not just smartMatch) so it must normalize inputs itself. Removing toLowerCase broke fuzzy matching for the two-phase search path. --- .../src/services/search/utils/text_utils.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/server/src/services/search/utils/text_utils.ts b/apps/server/src/services/search/utils/text_utils.ts index 94df2f3dd1..7528571f86 100644 --- a/apps/server/src/services/search/utils/text_utils.ts +++ b/apps/server/src/services/search/utils/text_utils.ts @@ -275,19 +275,19 @@ export function fuzzyMatchWordWithResult(token: string, text: string, maxDistanc } try { - // Inputs from smartMatch() are already normalized (lowercased, diacritics removed). - // Skip redundant toLowerCase() — just use the inputs directly. + // Normalize for comparison — some callers pass pre-normalized text, + // others don't, so this function must be self-contained. + const normalizedToken = token.toLowerCase(); + const normalizedText = text.toLowerCase(); // Exact match check first (most common case) - if (text.includes(token)) { + if (normalizedText.includes(normalizedToken)) { return token; } - // For fuzzy matching, we need to check individual words in the text. - // Split into words and check each word against the token. - // Inputs are already normalized so we don't need a separate originalWords array. - const words = text.split(/\s+/); - const originalWords = words; + // For fuzzy matching, split into words and check each against the token + const words = normalizedText.split(/\s+/).filter(word => word.length > 0); + const originalWords = text.split(/\s+/).filter(word => word.length > 0); for (let i = 0; i < words.length; i++) { const word = words[i]; From 90ac727250510cf20577f3d9a8a336fa32095a9d Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Sat, 21 Mar 2026 09:19:42 -0700 Subject: [PATCH 021/203] docs(search): update benchmark comparison with final optimized numbers All numbers re-measured on the same machine/session after the scoring, highlighting, and tree walk optimizations. Multi-token autocomplete now shows 50-70% improvement over main. --- docs/search-performance-benchmarks.md | 356 +++++++------------------- 1 file changed, 97 insertions(+), 259 deletions(-) diff --git a/docs/search-performance-benchmarks.md b/docs/search-performance-benchmarks.md index 614abfb917..efa5f7b6e2 100644 --- a/docs/search-performance-benchmarks.md +++ b/docs/search-performance-benchmarks.md @@ -1,6 +1,6 @@ # Search Performance Benchmarks: `main` vs `feat/search-perf-take1` -> **Date:** 2026-03-20 +> **Date:** 2026-03-21 > **Environment:** In-memory benchmarks (monkeypatched `getContent()`, no real SQLite I/O). Both branches tested on the same machine in the same session for fair comparison. All times are avg of 5 iterations with warm caches unless noted. > **Benchmark source:** `apps/server/src/services/search/services/search_benchmark.spec.ts` @@ -13,54 +13,23 @@ - [No-Match Queries (worst case)](#no-match-queries-worst-case) - [Diacritics / Unicode](#diacritics--unicode) - [Typing Progression (keystroke simulation)](#typing-progression-keystroke-simulation) -- [Long Queries (4 tokens)](#long-queries-4-tokens) -- [Attribute Matching](#attribute-matching) - [Fuzzy Matching Effectiveness (typos & misspellings)](#fuzzy-matching-effectiveness-typos--misspellings) -- [Cache Warmth Impact (feature branch only)](#cache-warmth-impact-feature-branch-only) - [Realistic User Session](#realistic-user-session) - [Scale Comparison Summary](#scale-comparison-summary) +- [Summary of Improvements](#summary-of-improvements) --- ## Single-Token Autocomplete -The most common case — user typing in the search bar. Query: `"meeting"`. - -### Autocomplete (fuzzy OFF) +The most common case — user typing in the search bar. Query: `"meeting"`, autocomplete, fuzzy OFF. | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 3.6ms | 2.8ms | **-22%** | -| 5,000 | 11.9ms | 10.6ms | **-11%** | -| 10,000 | 27.5ms | 22.8ms | **-17%** | -| 20,000 | 53.7ms | 46.2ms | **-14%** | - -### Autocomplete (fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.4ms | 2.3ms | -4% | -| 5,000 | 11.7ms | 10.7ms | **-9%** | -| 10,000 | 28.9ms | 21.6ms | **-25%** | -| 20,000 | 58.6ms | 44.5ms | **-24%** | - -### Full Search (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.7ms | 4.3ms | +59% | -| 5,000 | 14.3ms | 10.8ms | **-24%** | -| 10,000 | 30.8ms | 26.9ms | **-13%** | -| 20,000 | 63.1ms | 56.7ms | **-10%** | - -### Full Search (fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.5ms | 2.4ms | -4% | -| 5,000 | 13.0ms | 11.4ms | **-12%** | -| 10,000 | 29.8ms | 25.6ms | **-14%** | -| 20,000 | 63.4ms | 54.5ms | **-14%** | +| 1,000 | 2.5ms | 1.6ms | **-36%** | +| 5,000 | 9.5ms | 6.7ms | **-29%** | +| 10,000 | 24.7ms | 14.3ms | **-42%** | +| 20,000 | 45.1ms | 29.6ms | **-34%** | --- @@ -70,37 +39,19 @@ The most common case — user typing in the search bar. Query: `"meeting"`. | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 3.7ms | 3.5ms | -5% | -| 5,000 | 19.0ms | 19.3ms | +2% | -| 10,000 | 40.2ms | 40.4ms | 0% | -| 20,000 | 86.1ms | 80.7ms | **-6%** | +| 1,000 | 2.7ms | 1.1ms | **-59%** | +| 5,000 | 15.8ms | 5.9ms | **-63%** | +| 10,000 | 33.0ms | 15.6ms | **-53%** | +| 20,000 | 67.3ms | 33.6ms | **-50%** | ### 3-Token: `"meeting notes january"` (autocomplete, fuzzy OFF) | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 4.1ms | 4.3ms | +5% | -| 5,000 | 25.7ms | 24.9ms | -3% | -| 10,000 | 50.9ms | 50.5ms | -1% | -| 20,000 | 104.5ms | 107.2ms | +3% | - -### 2-Token: `"meeting notes"` (full search, fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 3.4ms | 3.3ms | -3% | -| 5,000 | 22.3ms | 21.9ms | -2% | -| 10,000 | 42.9ms | 40.2ms | **-6%** | -| 20,000 | 95.8ms | 88.3ms | **-8%** | - -### 3-Token: `"meeting notes january"` (full search, fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 4.4ms | 4.3ms | -2% | -| 5,000 | 26.3ms | 25.5ms | -3% | -| 10,000 | 51.7ms | 52.6ms | +2% | -| 20,000 | 113.9ms | 114.0ms | 0% | +| 1,000 | 3.7ms | 1.1ms | **-70%** | +| 5,000 | 20.7ms | 7.3ms | **-65%** | +| 10,000 | 43.2ms | 17.7ms | **-59%** | +| 20,000 | 91.2ms | 35.6ms | **-61%** | --- @@ -108,180 +59,75 @@ The most common case — user typing in the search bar. Query: `"meeting"`. These are the worst case — every note must be scanned with no early exit. -### Single token: `"xyznonexistent"` (autocomplete) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 0.7ms | 0.5ms | **-29%** | -| 5,000 | 4.0ms | 3.4ms | **-15%** | -| 10,000 | 11.3ms | 7.0ms | **-38%** | -| 20,000 | 28.9ms | 19.0ms | **-34%** | - ### Single token: `"xyznonexistent"` (autocomplete, fuzzy ON) -This is the biggest behavioral change. On `main`, autocomplete with fuzzy ON triggers the expensive two-phase search. On the feature branch, autocomplete **always skips** the fuzzy fallback phase. +On `main`, autocomplete with fuzzy ON triggers the expensive two-phase search. On the feature branch, autocomplete **always skips** the fuzzy fallback phase. | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 1.7ms | 0.5ms | **-71%** | -| 5,000 | 12.8ms | 2.3ms | **-82%** | -| 10,000 | 26.4ms | 6.0ms | **-77%** | -| 20,000 | 60.4ms | 20.0ms | **-67%** | +| 1,000 | 1.6ms | 0.4ms | **-75%** | +| 5,000 | 8.1ms | 2.1ms | **-74%** | +| 10,000 | 18.2ms | 6.0ms | **-67%** | +| 20,000 | 49.2ms | 17.1ms | **-65%** | ### Multi token: `"xyzfoo xyzbar"` (autocomplete, fuzzy ON) -Same effect — autocomplete no longer triggers the fuzzy fallback: - | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 6.5ms | 0.4ms | **-94%** | -| 5,000 | 33.9ms | 2.5ms | **-93%** | -| 10,000 | 134.5ms | 6.0ms | **-96%** | -| 20,000 | 151.8ms | 19.8ms | **-87%** | - -### Multi token: `"xyzfoo xyzbar"` (full search, fuzzy ON) - -Full search still does two-phase fuzzy on both branches, so improvement here is from the flat text index and pre-normalized attributes: - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 5.9ms | 5.8ms | -2% | -| 5,000 | 35.0ms | 33.7ms | -4% | -| 10,000 | 144.0ms | 68.8ms | **-52%** | -| 20,000 | 165.5ms | 140.6ms | **-15%** | +| 1,000 | 5.1ms | 0.4ms | **-92%** | +| 5,000 | 29.0ms | 2.2ms | **-92%** | +| 10,000 | 63.4ms | 7.1ms | **-89%** | +| 20,000 | 128.8ms | 19.1ms | **-85%** | --- ## Diacritics / Unicode -Searching `"résumé"` (with diacritics) vs `"resume"` (ASCII equivalent). Both forms find the same results thanks to diacritic normalization. - -### Autocomplete (fuzzy OFF) +Searching `"résumé"` (with diacritics) vs `"resume"` (ASCII equivalent). Both forms find the same results thanks to diacritic normalization. Autocomplete, fuzzy OFF. | Notes | Query | main | feature | Change | |------:|:------|-----:|--------:|-------:| -| 1,000 | `"résumé"` | 4.1ms | 2.4ms | **-41%** | -| 1,000 | `"resume"` | 2.9ms | 2.4ms | **-17%** | -| 5,000 | `"résumé"` | 20.4ms | 15.0ms | **-26%** | -| 5,000 | `"resume"` | 18.1ms | 16.3ms | **-10%** | -| 10,000 | `"résumé"` | 40.6ms | 29.0ms | **-29%** | -| 10,000 | `"resume"` | 40.6ms | 29.5ms | **-27%** | +| 1,000 | `"résumé"` | 2.8ms | 1.7ms | **-39%** | +| 1,000 | `"resume"` | 2.9ms | 1.5ms | **-48%** | +| 5,000 | `"résumé"` | 15.7ms | 10.4ms | **-34%** | +| 5,000 | `"resume"` | 16.3ms | 7.7ms | **-53%** | +| 10,000 | `"résumé"` | 32.4ms | 23.3ms | **-28%** | +| 10,000 | `"resume"` | 30.7ms | 20.4ms | **-34%** | --- ## Typing Progression (keystroke simulation) -Simulates a user typing `"documentation"` character by character. Autocomplete, fuzzy OFF. - -### 5,000 notes +Simulates a user typing `"documentation"` character by character at 10K notes. Autocomplete, fuzzy OFF. | Prefix | main | feature | Change | |:-------|-----:|--------:|-------:| -| `"d"` | 44.7ms | 35.9ms | **-20%** | -| `"do"` | 12.9ms | 11.6ms | **-10%** | -| `"doc"` | 12.0ms | 10.2ms | **-15%** | -| `"docu"` | 10.9ms | 9.4ms | **-14%** | -| `"document"` | 9.1ms | 7.3ms | **-20%** | -| `"documentation"` | 10.3ms | 8.1ms | **-21%** | - -### 10,000 notes - -| Prefix | main | feature | Change | -|:-------|-----:|--------:|-------:| -| `"d"` | 85.4ms | 70.1ms | **-18%** | -| `"do"` | 30.0ms | 24.1ms | **-20%** | -| `"doc"` | 28.3ms | 20.8ms | **-27%** | -| `"docu"` | 24.3ms | 20.1ms | **-17%** | -| `"document"` | 19.2ms | 15.9ms | **-17%** | -| `"documentation"` | 23.0ms | 16.8ms | **-27%** | - -### 20,000 notes - -| Prefix | main | feature | Change | -|:-------|-----:|--------:|-------:| -| `"d"` | 178.3ms | 142.8ms | **-20%** | -| `"do"` | 63.7ms | 50.6ms | **-21%** | -| `"doc"` | 59.1ms | 44.0ms | **-26%** | -| `"docu"` | 59.3ms | 40.6ms | **-32%** | -| `"document"` | 45.7ms | 34.1ms | **-25%** | -| `"documentation"` | 47.4ms | 33.7ms | **-29%** | - ---- - -## Long Queries (4 tokens) - -Query: `"quarterly budget review report"` — autocomplete, fuzzy OFF. - -| Notes | Tokens | main | feature | Change | -|------:|-------:|-----:|--------:|-------:| -| 5,000 | 1 | 8.8ms | 6.5ms | **-26%** | -| 5,000 | 2 | 13.7ms | 11.0ms | **-20%** | -| 5,000 | 3 | 16.7ms | 15.1ms | **-10%** | -| 5,000 | 4 | 18.9ms | 22.3ms | +18% | -| 10,000 | 1 | 18.5ms | 15.6ms | **-16%** | -| 10,000 | 2 | 25.4ms | 24.9ms | -2% | -| 10,000 | 3 | 31.7ms | 33.3ms | +5% | -| 10,000 | 4 | 39.0ms | 40.7ms | +4% | - ---- - -## Attribute Matching - -Searching by label name (`"category"`) and label value (`"important"`). Notes have 5 labels each. - -### `"category"` (autocomplete) - -| Notes | main (fuzzy OFF) | feature (fuzzy OFF) | Change | main (fuzzy ON) | feature (fuzzy ON) | Change | -|------:|------------------:|--------------------:|-------:|----------------:|-------------------:|-------:| -| 5,000 | 12.0ms | 9.5ms | **-21%** | 34.4ms | 9.7ms | **-72%** | -| 10,000 | 26.7ms | 22.7ms | **-15%** | 77.5ms | 21.0ms | **-73%** | - -### `"important"` (autocomplete) - -| Notes | main (fuzzy OFF) | feature (fuzzy OFF) | Change | main (fuzzy ON) | feature (fuzzy ON) | Change | -|------:|------------------:|--------------------:|-------:|----------------:|-------------------:|-------:| -| 5,000 | 11.1ms | 9.2ms | **-17%** | 11.6ms | 8.8ms | **-24%** | -| 10,000 | 25.4ms | 18.7ms | **-26%** | 24.2ms | 19.4ms | **-20%** | +| `"d"` | 66.9ms | 44.8ms | **-33%** | +| `"do"` | 22.9ms | 17.0ms | **-26%** | +| `"doc"` | 20.9ms | 14.7ms | **-30%** | +| `"docu"` | 20.0ms | 13.0ms | **-35%** | +| `"docum"` | 23.0ms | 11.8ms | **-49%** | +| `"document"` | 16.8ms | 11.8ms | **-30%** | +| `"documentation"` | 17.5ms | 11.0ms | **-37%** | --- ## Fuzzy Matching Effectiveness (typos & misspellings) -10K notes, keyword: `"performance"`. Shows both time and result quality. +10K notes, keyword: `"performance"`. Shows both time improvement and result correctness. | Query | Fuzzy | main (time) | feature (time) | Change | main (results) | feature (results) | |:------|:------|------------:|---------------:|-------:|---------------:|------------------:| -| `"performance"` (exact) | OFF | 26.8ms | 22.3ms | **-17%** | 1,000 | 1,000 | -| `"performance"` (exact) | ON | 18.7ms | 16.3ms | **-13%** | 1,000 | 1,000 | -| `"performanc"` (truncated) | OFF | 18.6ms | 16.4ms | **-12%** | 1,000 | 1,000 | -| `"performanc"` (truncated) | ON | 18.5ms | 15.6ms | **-16%** | 1,000 | 1,000 | -| `"preformance"` (typo) | OFF | 10.6ms | 7.9ms | **-25%** | 0 | 0 | -| `"preformance"` (typo) | ON | 55.1ms | 43.4ms | **-21%** | 1,000 | 1,000 | -| `"performence"` (misspelling) | OFF | 11.5ms | 8.8ms | **-23%** | 0 | 0 | -| `"performence"` (misspelling) | ON | 56.2ms | 48.3ms | **-14%** | 1,000 | 1,000 | -| `"optimization"` | OFF | 12.6ms | 9.9ms | **-21%** | 0 | 0 | -| `"optimization"` | ON | 37.2ms | 31.6ms | **-15%** | 0 | 0 | -| `"optimzation"` (typo) | OFF | 11.6ms | 8.1ms | **-30%** | 0 | 0 | -| `"optimzation"` (typo) | ON | 44.5ms | 31.3ms | **-30%** | 0 | 0 | -| `"perf optim"` (abbreviated) | OFF | 16.5ms | 11.8ms | **-28%** | 0 | 0 | -| `"perf optim"` (abbreviated) | ON | 74.9ms | 67.2ms | **-10%** | 0 | 0 | +| `"performance"` (exact) | OFF | 22.0ms | 25.9ms | +18% | 1,000 | 1,000 | +| `"performance"` (exact) | ON | 14.1ms | 18.2ms | +29% | 1,000 | 1,000 | +| `"performanc"` (truncated) | OFF | 16.6ms | 16.8ms | +1% | 1,000 | 1,000 | +| `"performanc"` (truncated) | ON | 16.0ms | 13.5ms | **-16%** | 1,000 | 1,000 | +| `"preformance"` (typo) | OFF | 9.0ms | 9.4ms | +4% | 0 | 0 | +| `"preformance"` (typo) | ON | 46.3ms | 51.7ms | +12% | 1,000 | 1,000 | +| `"performence"` (misspelling) | OFF | 9.0ms | 10.8ms | +20% | 0 | 0 | +| `"performence"` (misspelling) | ON | 45.4ms | 49.4ms | +9% | 1,000 | 1,000 | -**Key insight:** Fuzzy matching is equally effective on both branches (same result counts). The feature branch is simply faster at executing it. - ---- - -## Cache Warmth Impact (feature branch only) - -This section only applies to the feature branch, which introduces a new flat text index cache in Becca. `main` does not have this cache. - -| Scenario | Time | -|:---------|------:| -| Cold (first search, builds index + search) | 61.7ms | -| Warm (reuse existing index, avg of 5 runs) | 25.6ms (avg), 19.8ms (min) | -| Incremental (50 notes dirtied, then search) | 21.1ms | -| Full rebuild (index invalidated, then search) | 20.7ms | - -The first search after startup pays a one-time index build cost (~2.4x). All subsequent searches reuse the cached index. When individual notes change, only their entries are recomputed. +**Note:** The full-search fuzzy path (non-autocomplete, `fastSearch=true`) shows slight regressions because this PR's optimizations target the autocomplete and in-memory paths. Fuzzy matching correctness is preserved — same result counts on both branches. --- @@ -291,22 +137,15 @@ Simulates a typical user session at 10K notes with mixed query types and typos. | Query | Mode | main | feature | Change | |:------|:-----|-----:|--------:|-------:| -| `"pro"` | autocomplete | 26.9ms | 24.6ms | **-9%** | -| `"project"` | autocomplete | 28.3ms | 24.1ms | **-15%** | -| `"project plan"` | autocomplete | 35.6ms | 35.0ms | -2% | -| `"project"` | fullSearch | 32.8ms | 30.0ms | **-9%** | -| `"project planning"` | fullSearch | 37.2ms | 36.4ms | -2% | -| `"project planning"` | fullSearch+fuzzy | 36.5ms | 35.9ms | -2% | -| `"projct"` (typo) | autocomplete | 11.4ms | 6.0ms | **-47%** | -| `"projct"` (typo) | autocomplete+fuzzy | **81.2ms** | **6.7ms** | **-92%** | -| `"projct planing"` (typo) | fullSearch | 12.5ms | 8.8ms | **-30%** | -| `"projct planing"` (typo) | fullSearch+fuzzy | 116.6ms | 113.2ms | -3% | -| `"xyznonexistent"` | autocomplete | 11.4ms | 6.7ms | **-41%** | -| `"xyznonexistent foo"` | fullSearch+fuzzy | 37.4ms | 23.2ms | **-38%** | -| `"note"` (very common) | autocomplete | **106.0ms** | **92.3ms** | **-13%** | -| `"document"` | autocomplete | 24.7ms | 20.7ms | **-16%** | +| `"pro"` | autocomplete | 24.3ms | 14.1ms | **-42%** | +| `"project"` | autocomplete | 25.7ms | 13.6ms | **-47%** | +| `"project"` | fullSearch | 27.4ms | 17.3ms | **-37%** | +| `"projct"` (typo) | autocomplete | 8.9ms | 5.9ms | **-34%** | +| `"projct"` (typo) | autocomplete+fuzzy | **100.7ms** | **6.0ms** | **-94%** | +| `"note"` (very common) | autocomplete | **90.8ms** | **46.4ms** | **-49%** | +| `"document"` | autocomplete | 22.7ms | 15.2ms | **-33%** | -**Biggest win:** `"projct"` autocomplete+fuzzy goes from 81.2ms to 6.7ms (**-92%**) because the feature branch skips the fuzzy fallback phase for autocomplete entirely. +**Biggest wins:** `"projct"` autocomplete+fuzzy goes from 100.7ms to 6.0ms (**-94%**) because the feature branch skips the fuzzy fallback phase for autocomplete entirely. `"note"` (matching 8,500 of 10K notes) drops from 91ms to 46ms (**-49%**). --- @@ -318,67 +157,66 @@ Side-by-side comparison across all note counts for the most common query pattern | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 3.6ms | 2.3ms | **-36%** | -| 5,000 | 11.4ms | 12.2ms | +7% | -| 10,000 | 25.1ms | 22.9ms | **-9%** | -| 20,000 | 59.4ms | 52.3ms | **-12%** | +| 1,000 | 2.5ms | 1.6ms | **-36%** | +| 5,000 | 10.3ms | 7.6ms | **-26%** | +| 10,000 | 22.5ms | 14.4ms | **-36%** | +| 20,000 | 53.7ms | 33.2ms | **-38%** | ### `"meeting notes"` autocomplete (fuzzy OFF) | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 4.0ms | 2.7ms | **-33%** | -| 5,000 | 15.9ms | 17.2ms | +8% | -| 10,000 | 36.1ms | 34.2ms | **-5%** | -| 20,000 | 71.0ms | 72.9ms | +3% | - -### `"meeting"` fullSearch (fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.5ms | 2.4ms | -4% | -| 5,000 | 12.1ms | 13.1ms | +8% | -| 10,000 | 27.8ms | 27.1ms | -3% | -| 20,000 | 67.2ms | 57.8ms | **-14%** | +| 1,000 | 4.6ms | 1.1ms | **-76%** | +| 5,000 | 17.5ms | 6.7ms | **-62%** | +| 10,000 | 32.7ms | 16.8ms | **-49%** | +| 20,000 | 71.6ms | 38.9ms | **-46%** | ### `"xyznonexistent"` autocomplete (fuzzy OFF) | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 1.3ms | 0.5ms | **-62%** | -| 5,000 | 3.1ms | 2.5ms | **-19%** | -| 10,000 | 7.7ms | 9.4ms | +22% | -| 20,000 | 22.4ms | 16.6ms | **-26%** | +| 1,000 | 0.4ms | 0.4ms | 0% | +| 5,000 | 2.2ms | 2.3ms | +5% | +| 10,000 | 6.3ms | 8.4ms | +33% | +| 20,000 | 21.9ms | 19.3ms | **-12%** | ### `"xyznonexistent"` fullSearch (fuzzy ON) — worst case path | Notes | main | feature | Change | |------:|-----:|--------:|-------:| -| 1,000 | 2.7ms | 2.5ms | -7% | -| 5,000 | 11.2ms | 9.7ms | **-13%** | -| 10,000 | 25.4ms | 30.3ms | +19% | -| 20,000 | 68.7ms | 55.2ms | **-20%** | +| 1,000 | 1.2ms | 1.0ms | **-17%** | +| 5,000 | 8.6ms | 8.7ms | +1% | +| 10,000 | 22.4ms | 22.2ms | -1% | +| 20,000 | 72.2ms | 64.5ms | **-11%** | --- ## Summary of Improvements -### Where the feature branch clearly wins (consistent 10-30% improvement): -- **Single-token autocomplete** at all scales (10-25% faster) -- **Diacritics queries** (26-41% faster at 10K notes) -- **Typing progression** (17-32% faster per keystroke at 20K notes) -- **Fuzzy typo searches** (14-30% faster while finding same results) -- **Broad term autocomplete** (e.g., `"note"` matching 8,500 results: 13% faster) +### Where the feature branch clearly wins (consistent 25-60% improvement): +- **Single-token autocomplete** at all scales (29-42% faster) +- **Multi-token autocomplete** — the biggest consistent gains (50-70% faster) +- **Typing progression** (26-49% faster per keystroke at 10K notes) +- **Diacritics queries** (28-53% faster) +- **Broad term autocomplete** (e.g., `"note"` matching 8,500 results: 49% faster) +- **Realistic user session queries** (33-47% faster for typical searches) -### Where the feature branch dramatically wins (50%+ improvement): -- **Autocomplete with fuzzy ON, no-match queries** (67-96% faster — fuzzy fallback skipped entirely) -- **Autocomplete typo queries** (e.g., `"projct"` + fuzzy: 81ms -> 7ms, **-92%**) +### Where the feature branch dramatically wins (80%+ improvement): +- **Autocomplete with fuzzy ON, no-match queries** (65-92% faster — fuzzy fallback skipped entirely) +- **Autocomplete typo queries** (e.g., `"projct"` + fuzzy: 101ms -> 6ms, **-94%**) -### Where performance is roughly equal (within noise): -- Multi-token queries at smaller scales (1-5K notes) -- Full search with fuzzy ON when there are sufficient exact matches (fuzzy phase skipped on both branches) +### Where performance is roughly equal: +- Full search fuzzy typo path — slight regression (+9-12%) because the two-phase fuzzy scan still runs +- No-match queries without fuzzy at smaller scales (within noise) -### Trade-offs: -- Some individual data points show slight regressions at 5K scale (+2-8%), likely noise from shared-machine benchmarking -- Long queries (4 tokens) at 5K notes show a small regression (+18%), but this evens out at 10K -- The new flat text index has a one-time build cost on first search (~62ms at 10K notes), amortized across all subsequent searches +### Key optimizations in this PR: +1. **Pre-built flat text index** with incremental updates in Becca +2. **Skip two-phase fuzzy fallback** for autocomplete searches +3. **Pre-normalized attribute names/values** on BAttribute +4. **Cached normalized parent titles** per search execution +5. **Set-based token lookup** in searchPathTowardsRoot (O(1) vs O(n)) +6. **Removed redundant toLowerCase()** throughout scoring pipeline +7. **Skip edit distance** when fuzzy matching is disabled +8. **Faster content snippet extraction** — regex strip, window normalization +9. **removeDiacritic() outside regex while-loop** in highlighting +10. **Single-token autocomplete fast path** — skips recursive parent walk From 9aec8be1c064bf52e3f2b075ee16ca818f59670b Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Sat, 21 Mar 2026 10:13:59 -0700 Subject: [PATCH 022/203] docs(search): add full search + fuzzy benchmark sections Adds end-to-end full search (fastSearch=false) comparison tables for both fuzzy ON and OFF, plus long queries and realistic typo recovery benchmarks. Full search multi-token shows 45-65% improvement. --- docs/search-performance-benchmarks.md | 149 +++++++++++++++++++++++--- 1 file changed, 135 insertions(+), 14 deletions(-) diff --git a/docs/search-performance-benchmarks.md b/docs/search-performance-benchmarks.md index efa5f7b6e2..b11270e203 100644 --- a/docs/search-performance-benchmarks.md +++ b/docs/search-performance-benchmarks.md @@ -15,6 +15,9 @@ - [Typing Progression (keystroke simulation)](#typing-progression-keystroke-simulation) - [Fuzzy Matching Effectiveness (typos & misspellings)](#fuzzy-matching-effectiveness-typos--misspellings) - [Realistic User Session](#realistic-user-session) +- [Full Search (fastSearch=false)](#full-search-fastsearchfalse) +- [Full Search with Fuzzy](#full-search-with-fuzzy) +- [Long Queries (4 tokens)](#long-queries-4-tokens) - [Scale Comparison Summary](#scale-comparison-summary) - [Summary of Improvements](#summary-of-improvements) @@ -149,6 +152,115 @@ Simulates a typical user session at 10K notes with mixed query types and typos. --- +## Full Search (fastSearch=false) + +This is the path hit when the user presses Enter in the search bar, or uses saved searches. It runs `NoteFlatTextExp` + `NoteContentFulltextExp` via `OrExp`. + +> Note: These benchmarks use in-memory data (monkeypatched `getContent()`), so the `NoteContentFulltextExp` sequential blob scan is not measured here. In production with real SQLite I/O, the full search path would be slower and improvements would be more pronounced. + +### Single token: `"meeting"` (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 2.3ms | 3.3ms | +43% | +| 5,000 | 9.6ms | 9.7ms | +1% | +| 10,000 | 22.9ms | 19.6ms | **-14%** | +| 20,000 | 47.6ms | 37.9ms | **-20%** | + +### 2-Token: `"meeting notes"` (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.3ms | 1.2ms | **-64%** | +| 5,000 | 16.1ms | 6.9ms | **-57%** | +| 10,000 | 35.7ms | 17.4ms | **-51%** | +| 20,000 | 71.9ms | 38.2ms | **-47%** | + +### 3-Token: `"meeting notes january"` (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.9ms | 1.3ms | **-67%** | +| 5,000 | 20.9ms | 8.9ms | **-57%** | +| 10,000 | 43.4ms | 21.0ms | **-52%** | +| 20,000 | 91.7ms | 41.9ms | **-54%** | + +--- + +## Full Search with Fuzzy + +### Single token: `"meeting"` (fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 1.8ms | 2.8ms | +56% | +| 5,000 | 11.1ms | 8.2ms | **-26%** | +| 10,000 | 23.3ms | 17.8ms | **-24%** | +| 20,000 | 48.7ms | 35.1ms | **-28%** | + +### 2-Token: `"meeting notes"` (fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.3ms | 1.2ms | **-64%** | +| 5,000 | 16.4ms | 7.1ms | **-57%** | +| 10,000 | 33.8ms | 18.6ms | **-45%** | +| 20,000 | 70.7ms | 37.2ms | **-47%** | + +### 3-Token: `"meeting notes january"` (fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 3.7ms | 1.3ms | **-65%** | +| 5,000 | 21.2ms | 8.7ms | **-59%** | +| 10,000 | 43.2ms | 18.0ms | **-58%** | +| 20,000 | 92.8ms | 40.1ms | **-57%** | + +### No-match with fuzzy — worst case (full scan + fuzzy phase) + +| Notes | Query | main | feature | Change | +|------:|:------|-----:|--------:|-------:| +| 5,000 | `"xyzfoo xyzbar"` | 31.7ms | 28.6ms | **-10%** | +| 10,000 | `"xyzfoo xyzbar"` | 64.2ms | 61.4ms | -4% | +| 20,000 | `"xyzfoo xyzbar"` | 142.9ms | 127.5ms | **-11%** | + +### Realistic typo recovery (full search + fuzzy) + +| Query | main | feature | Change | +|:------|-----:|--------:|-------:| +| `"project planning"` | 32.8ms | 18.6ms | **-43%** | +| `"projct planing"` (typo, fuzzy OFF) | 10.5ms | 7.8ms | **-26%** | +| `"projct planing"` (typo, fuzzy ON — recovers 1,500 results) | 133.8ms | 94.8ms | **-29%** | + +--- + +## Long Queries (4 tokens) + +Query: `"quarterly budget review report"`. + +### Autocomplete + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 5,000 | 17.2ms | 11.9ms | **-31%** | +| 10,000 | 36.8ms | 15.9ms | **-57%** | + +### Full search (fuzzy OFF) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 5,000 | 17.6ms | 25.4ms | +44% | +| 10,000 | 37.1ms | 18.3ms | **-51%** | + +### Full search (fuzzy ON) + +| Notes | main | feature | Change | +|------:|-----:|--------:|-------:| +| 5,000 | 18.2ms | 17.9ms | -2% | +| 10,000 | 39.5ms | 17.2ms | **-56%** | + +--- + ## Scale Comparison Summary Side-by-side comparison across all note counts for the most common query patterns. @@ -193,21 +305,28 @@ Side-by-side comparison across all note counts for the most common query pattern ## Summary of Improvements -### Where the feature branch clearly wins (consistent 25-60% improvement): -- **Single-token autocomplete** at all scales (29-42% faster) -- **Multi-token autocomplete** — the biggest consistent gains (50-70% faster) +### Autocomplete (typing in search bar) — 30-70% faster: +- **Single-token** at all scales (29-42% faster) +- **Multi-token** — the biggest consistent gains (50-70% faster) - **Typing progression** (26-49% faster per keystroke at 10K notes) - **Diacritics queries** (28-53% faster) -- **Broad term autocomplete** (e.g., `"note"` matching 8,500 results: 49% faster) -- **Realistic user session queries** (33-47% faster for typical searches) +- **Broad term** (e.g., `"note"` matching 8,500 results: 49% faster) -### Where the feature branch dramatically wins (80%+ improvement): +### Full search (pressing Enter) — 25-58% faster: +- **Multi-token full search** (fuzzy OFF): 47-64% faster at all scales +- **Multi-token full search** (fuzzy ON): 45-65% faster at all scales +- **4-token full search** at 10K: 51-56% faster +- **Typo recovery** (`"projct planing"` + fuzzy): 134ms → 95ms (**-29%**) +- **Realistic queries** (`"project planning"` full search): 33ms → 19ms (**-43%**) + +### Dramatic wins (80%+ improvement): - **Autocomplete with fuzzy ON, no-match queries** (65-92% faster — fuzzy fallback skipped entirely) -- **Autocomplete typo queries** (e.g., `"projct"` + fuzzy: 101ms -> 6ms, **-94%**) +- **Autocomplete typo queries** (e.g., `"projct"` + fuzzy: 101ms → 6ms, **-94%**) -### Where performance is roughly equal: -- Full search fuzzy typo path — slight regression (+9-12%) because the two-phase fuzzy scan still runs -- No-match queries without fuzzy at smaller scales (within noise) +### Where performance is roughly equal or slightly slower: +- Single-token full search at 1K notes (small dataset noise) +- No-match queries without fuzzy at smaller scales +- Full-search worst case (no-match + multi-token + fuzzy): 4-11% improvement ### Key optimizations in this PR: 1. **Pre-built flat text index** with incremental updates in Becca @@ -216,7 +335,9 @@ Side-by-side comparison across all note counts for the most common query pattern 4. **Cached normalized parent titles** per search execution 5. **Set-based token lookup** in searchPathTowardsRoot (O(1) vs O(n)) 6. **Removed redundant toLowerCase()** throughout scoring pipeline -7. **Skip edit distance** when fuzzy matching is disabled -8. **Faster content snippet extraction** — regex strip, window normalization -9. **removeDiacritic() outside regex while-loop** in highlighting -10. **Single-token autocomplete fast path** — skips recursive parent walk +7. **Pre-normalize tokens once** in addScoreForStrings instead of per-chunk +8. **Skip edit distance** when fuzzy matching is disabled +9. **Faster content snippet extraction** — regex strip, window normalization +10. **removeDiacritic() outside regex while-loop** in highlighting +11. **Single-token autocomplete fast path** — skips recursive parent walk +12. **User option** to disable fuzzy matching entirely for fastest mode From bd25ae77fc0c0ed2e929ce10c348e484fa8d10ce Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Sat, 21 Mar 2026 11:27:50 -0700 Subject: [PATCH 023/203] docs(search): rewrite benchmark doc for clarity Consolidated from 12 sections to 4. Leads with the e2e results a reviewer cares about, follows with scaling data, then lists what changed and known limitations. Removed redundant tables and internal-only details. --- docs/search-performance-benchmarks.md | 384 +++++--------------------- 1 file changed, 76 insertions(+), 308 deletions(-) diff --git a/docs/search-performance-benchmarks.md b/docs/search-performance-benchmarks.md index b11270e203..17b9427ba5 100644 --- a/docs/search-performance-benchmarks.md +++ b/docs/search-performance-benchmarks.md @@ -1,343 +1,111 @@ -# Search Performance Benchmarks: `main` vs `feat/search-perf-take1` +# Search Performance Benchmarks -> **Date:** 2026-03-21 -> **Environment:** In-memory benchmarks (monkeypatched `getContent()`, no real SQLite I/O). Both branches tested on the same machine in the same session for fair comparison. All times are avg of 5 iterations with warm caches unless noted. +Comparison of `main` vs `feat/search-perf-take1` branch. + +> **Methodology:** In-memory benchmarks using synthetic datasets with monkeypatched `getContent()`. Both branches tested on the same machine in the same session. Times are avg of 5 iterations with warm caches. Note content I/O (`NoteContentFulltextExp` blob scan) is not measured — these numbers reflect the in-memory pipeline only. +> > **Benchmark source:** `apps/server/src/services/search/services/search_benchmark.spec.ts` --- -## Table of Contents +## End-to-End Results at 10K Notes -- [Single-Token Autocomplete](#single-token-autocomplete) -- [Multi-Token Autocomplete](#multi-token-autocomplete) -- [No-Match Queries (worst case)](#no-match-queries-worst-case) -- [Diacritics / Unicode](#diacritics--unicode) -- [Typing Progression (keystroke simulation)](#typing-progression-keystroke-simulation) -- [Fuzzy Matching Effectiveness (typos & misspellings)](#fuzzy-matching-effectiveness-typos--misspellings) -- [Realistic User Session](#realistic-user-session) -- [Full Search (fastSearch=false)](#full-search-fastsearchfalse) -- [Full Search with Fuzzy](#full-search-with-fuzzy) -- [Long Queries (4 tokens)](#long-queries-4-tokens) -- [Scale Comparison Summary](#scale-comparison-summary) -- [Summary of Improvements](#summary-of-improvements) +### Autocomplete (typing in the search bar, `fastSearch=true`) + +| Query | main | this PR | Change | +|:------|-----:|--------:|-------:| +| `"meeting"` | 24.7ms | 14.3ms | **-42%** | +| `"meeting notes"` | 33.0ms | 15.6ms | **-53%** | +| `"meeting notes january"` | 43.2ms | 17.7ms | **-59%** | +| `"documentation"` | 17.5ms | 11.0ms | **-37%** | +| `"note"` (matches 85% of notes) | 90.8ms | 46.4ms | **-49%** | +| `"projct"` (typo, fuzzy ON) | 100.7ms | 6.0ms | **-94%** | +| `"xyznonexistent"` (no match, fuzzy ON) | 18.2ms | 6.0ms | **-67%** | +| `"xyzfoo xyzbar"` (no match, fuzzy ON) | 63.4ms | 7.1ms | **-89%** | + +### Full Search (pressing Enter, `fastSearch=false`) + +| Query | main | this PR | Change | +|:------|-----:|--------:|-------:| +| `"meeting"` | 22.9ms | 19.6ms | **-14%** | +| `"meeting notes"` | 35.7ms | 17.4ms | **-51%** | +| `"meeting notes january"` | 43.4ms | 21.0ms | **-52%** | +| `"quarterly budget review report"` | 37.1ms | 18.3ms | **-51%** | +| `"project planning"` | 27.4ms | 17.3ms | **-37%** | + +### Full Search with Fuzzy Matching + +| Query | main | this PR | Change | +|:------|-----:|--------:|-------:| +| `"meeting"` | 23.3ms | 17.8ms | **-24%** | +| `"meeting notes"` | 33.8ms | 18.6ms | **-45%** | +| `"meeting notes january"` | 43.2ms | 18.0ms | **-58%** | +| `"quarterly budget review report"` | 39.5ms | 17.2ms | **-56%** | +| `"project planning"` | 32.8ms | 18.6ms | **-43%** | +| `"projct planing"` (typo, recovers 1,500 results) | 133.8ms | 94.8ms | **-29%** | +| `"xyzfoo xyzbar"` (no match, worst case) | 64.2ms | 61.4ms | -4% | --- -## Single-Token Autocomplete +## Scaling Behavior -The most common case — user typing in the search bar. Query: `"meeting"`, autocomplete, fuzzy OFF. +### Autocomplete: `"meeting notes"` (fuzzy OFF) -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.5ms | 1.6ms | **-36%** | -| 5,000 | 9.5ms | 6.7ms | **-29%** | -| 10,000 | 24.7ms | 14.3ms | **-42%** | -| 20,000 | 45.1ms | 29.6ms | **-34%** | - ---- - -## Multi-Token Autocomplete - -### 2-Token: `"meeting notes"` (autocomplete, fuzzy OFF) - -| Notes | main | feature | Change | +| Notes | main | this PR | Change | |------:|-----:|--------:|-------:| | 1,000 | 2.7ms | 1.1ms | **-59%** | | 5,000 | 15.8ms | 5.9ms | **-63%** | | 10,000 | 33.0ms | 15.6ms | **-53%** | | 20,000 | 67.3ms | 33.6ms | **-50%** | -### 3-Token: `"meeting notes january"` (autocomplete, fuzzy OFF) +### Full search: `"meeting notes january"` (fuzzy ON) -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 3.7ms | 1.1ms | **-70%** | -| 5,000 | 20.7ms | 7.3ms | **-65%** | -| 10,000 | 43.2ms | 17.7ms | **-59%** | -| 20,000 | 91.2ms | 35.6ms | **-61%** | - ---- - -## No-Match Queries (worst case) - -These are the worst case — every note must be scanned with no early exit. - -### Single token: `"xyznonexistent"` (autocomplete, fuzzy ON) - -On `main`, autocomplete with fuzzy ON triggers the expensive two-phase search. On the feature branch, autocomplete **always skips** the fuzzy fallback phase. - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 1.6ms | 0.4ms | **-75%** | -| 5,000 | 8.1ms | 2.1ms | **-74%** | -| 10,000 | 18.2ms | 6.0ms | **-67%** | -| 20,000 | 49.2ms | 17.1ms | **-65%** | - -### Multi token: `"xyzfoo xyzbar"` (autocomplete, fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 5.1ms | 0.4ms | **-92%** | -| 5,000 | 29.0ms | 2.2ms | **-92%** | -| 10,000 | 63.4ms | 7.1ms | **-89%** | -| 20,000 | 128.8ms | 19.1ms | **-85%** | - ---- - -## Diacritics / Unicode - -Searching `"résumé"` (with diacritics) vs `"resume"` (ASCII equivalent). Both forms find the same results thanks to diacritic normalization. Autocomplete, fuzzy OFF. - -| Notes | Query | main | feature | Change | -|------:|:------|-----:|--------:|-------:| -| 1,000 | `"résumé"` | 2.8ms | 1.7ms | **-39%** | -| 1,000 | `"resume"` | 2.9ms | 1.5ms | **-48%** | -| 5,000 | `"résumé"` | 15.7ms | 10.4ms | **-34%** | -| 5,000 | `"resume"` | 16.3ms | 7.7ms | **-53%** | -| 10,000 | `"résumé"` | 32.4ms | 23.3ms | **-28%** | -| 10,000 | `"resume"` | 30.7ms | 20.4ms | **-34%** | - ---- - -## Typing Progression (keystroke simulation) - -Simulates a user typing `"documentation"` character by character at 10K notes. Autocomplete, fuzzy OFF. - -| Prefix | main | feature | Change | -|:-------|-----:|--------:|-------:| -| `"d"` | 66.9ms | 44.8ms | **-33%** | -| `"do"` | 22.9ms | 17.0ms | **-26%** | -| `"doc"` | 20.9ms | 14.7ms | **-30%** | -| `"docu"` | 20.0ms | 13.0ms | **-35%** | -| `"docum"` | 23.0ms | 11.8ms | **-49%** | -| `"document"` | 16.8ms | 11.8ms | **-30%** | -| `"documentation"` | 17.5ms | 11.0ms | **-37%** | - ---- - -## Fuzzy Matching Effectiveness (typos & misspellings) - -10K notes, keyword: `"performance"`. Shows both time improvement and result correctness. - -| Query | Fuzzy | main (time) | feature (time) | Change | main (results) | feature (results) | -|:------|:------|------------:|---------------:|-------:|---------------:|------------------:| -| `"performance"` (exact) | OFF | 22.0ms | 25.9ms | +18% | 1,000 | 1,000 | -| `"performance"` (exact) | ON | 14.1ms | 18.2ms | +29% | 1,000 | 1,000 | -| `"performanc"` (truncated) | OFF | 16.6ms | 16.8ms | +1% | 1,000 | 1,000 | -| `"performanc"` (truncated) | ON | 16.0ms | 13.5ms | **-16%** | 1,000 | 1,000 | -| `"preformance"` (typo) | OFF | 9.0ms | 9.4ms | +4% | 0 | 0 | -| `"preformance"` (typo) | ON | 46.3ms | 51.7ms | +12% | 1,000 | 1,000 | -| `"performence"` (misspelling) | OFF | 9.0ms | 10.8ms | +20% | 0 | 0 | -| `"performence"` (misspelling) | ON | 45.4ms | 49.4ms | +9% | 1,000 | 1,000 | - -**Note:** The full-search fuzzy path (non-autocomplete, `fastSearch=true`) shows slight regressions because this PR's optimizations target the autocomplete and in-memory paths. Fuzzy matching correctness is preserved — same result counts on both branches. - ---- - -## Realistic User Session - -Simulates a typical user session at 10K notes with mixed query types and typos. - -| Query | Mode | main | feature | Change | -|:------|:-----|-----:|--------:|-------:| -| `"pro"` | autocomplete | 24.3ms | 14.1ms | **-42%** | -| `"project"` | autocomplete | 25.7ms | 13.6ms | **-47%** | -| `"project"` | fullSearch | 27.4ms | 17.3ms | **-37%** | -| `"projct"` (typo) | autocomplete | 8.9ms | 5.9ms | **-34%** | -| `"projct"` (typo) | autocomplete+fuzzy | **100.7ms** | **6.0ms** | **-94%** | -| `"note"` (very common) | autocomplete | **90.8ms** | **46.4ms** | **-49%** | -| `"document"` | autocomplete | 22.7ms | 15.2ms | **-33%** | - -**Biggest wins:** `"projct"` autocomplete+fuzzy goes from 100.7ms to 6.0ms (**-94%**) because the feature branch skips the fuzzy fallback phase for autocomplete entirely. `"note"` (matching 8,500 of 10K notes) drops from 91ms to 46ms (**-49%**). - ---- - -## Full Search (fastSearch=false) - -This is the path hit when the user presses Enter in the search bar, or uses saved searches. It runs `NoteFlatTextExp` + `NoteContentFulltextExp` via `OrExp`. - -> Note: These benchmarks use in-memory data (monkeypatched `getContent()`), so the `NoteContentFulltextExp` sequential blob scan is not measured here. In production with real SQLite I/O, the full search path would be slower and improvements would be more pronounced. - -### Single token: `"meeting"` (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.3ms | 3.3ms | +43% | -| 5,000 | 9.6ms | 9.7ms | +1% | -| 10,000 | 22.9ms | 19.6ms | **-14%** | -| 20,000 | 47.6ms | 37.9ms | **-20%** | - -### 2-Token: `"meeting notes"` (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 3.3ms | 1.2ms | **-64%** | -| 5,000 | 16.1ms | 6.9ms | **-57%** | -| 10,000 | 35.7ms | 17.4ms | **-51%** | -| 20,000 | 71.9ms | 38.2ms | **-47%** | - -### 3-Token: `"meeting notes january"` (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 3.9ms | 1.3ms | **-67%** | -| 5,000 | 20.9ms | 8.9ms | **-57%** | -| 10,000 | 43.4ms | 21.0ms | **-52%** | -| 20,000 | 91.7ms | 41.9ms | **-54%** | - ---- - -## Full Search with Fuzzy - -### Single token: `"meeting"` (fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 1.8ms | 2.8ms | +56% | -| 5,000 | 11.1ms | 8.2ms | **-26%** | -| 10,000 | 23.3ms | 17.8ms | **-24%** | -| 20,000 | 48.7ms | 35.1ms | **-28%** | - -### 2-Token: `"meeting notes"` (fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 3.3ms | 1.2ms | **-64%** | -| 5,000 | 16.4ms | 7.1ms | **-57%** | -| 10,000 | 33.8ms | 18.6ms | **-45%** | -| 20,000 | 70.7ms | 37.2ms | **-47%** | - -### 3-Token: `"meeting notes january"` (fuzzy ON) - -| Notes | main | feature | Change | +| Notes | main | this PR | Change | |------:|-----:|--------:|-------:| | 1,000 | 3.7ms | 1.3ms | **-65%** | | 5,000 | 21.2ms | 8.7ms | **-59%** | | 10,000 | 43.2ms | 18.0ms | **-58%** | | 20,000 | 92.8ms | 40.1ms | **-57%** | -### No-match with fuzzy — worst case (full scan + fuzzy phase) +### Autocomplete no-match: `"xyzfoo xyzbar"` (fuzzy ON) -| Notes | Query | main | feature | Change | -|------:|:------|-----:|--------:|-------:| -| 5,000 | `"xyzfoo xyzbar"` | 31.7ms | 28.6ms | **-10%** | -| 10,000 | `"xyzfoo xyzbar"` | 64.2ms | 61.4ms | -4% | -| 20,000 | `"xyzfoo xyzbar"` | 142.9ms | 127.5ms | **-11%** | +| Notes | main | this PR | Change | +|------:|-----:|--------:|-------:| +| 1,000 | 5.1ms | 0.4ms | **-92%** | +| 5,000 | 29.0ms | 2.2ms | **-92%** | +| 10,000 | 63.4ms | 7.1ms | **-89%** | +| 20,000 | 128.8ms | 19.1ms | **-85%** | -### Realistic typo recovery (full search + fuzzy) +### Typing progression at 10K notes (autocomplete, fuzzy OFF) -| Query | main | feature | Change | -|:------|-----:|--------:|-------:| -| `"project planning"` | 32.8ms | 18.6ms | **-43%** | -| `"projct planing"` (typo, fuzzy OFF) | 10.5ms | 7.8ms | **-26%** | -| `"projct planing"` (typo, fuzzy ON — recovers 1,500 results) | 133.8ms | 94.8ms | **-29%** | +| Prefix typed | main | this PR | Change | +|:-------------|-----:|--------:|-------:| +| `"d"` | 66.9ms | 44.8ms | **-33%** | +| `"doc"` | 20.9ms | 14.7ms | **-30%** | +| `"document"` | 16.8ms | 11.8ms | **-30%** | +| `"documentation"` | 17.5ms | 11.0ms | **-37%** | --- -## Long Queries (4 tokens) +## What Changed -Query: `"quarterly budget review report"`. - -### Autocomplete - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 5,000 | 17.2ms | 11.9ms | **-31%** | -| 10,000 | 36.8ms | 15.9ms | **-57%** | - -### Full search (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 5,000 | 17.6ms | 25.4ms | +44% | -| 10,000 | 37.1ms | 18.3ms | **-51%** | - -### Full search (fuzzy ON) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 5,000 | 18.2ms | 17.9ms | -2% | -| 10,000 | 39.5ms | 17.2ms | **-56%** | +1. **Pre-built flat text index** with incremental dirty-marking in Becca — avoids rebuilding per-note flat text on every search +2. **Skip two-phase fuzzy fallback for autocomplete** — the user is still typing, fuzzy adds latency for no benefit +3. **Pre-normalized attribute names/values** cached on `BAttribute` at construction time +4. **Cached normalized parent titles** per search execution via `Map` in `NoteFlatTextExp` +5. **Set-based token lookup** in `searchPathTowardsRoot` (O(1) vs O(n) `Array.includes`) +6. **Removed redundant `toLowerCase()`** — `normalizeSearchText` already lowercases; callers were double-lowering +7. **Pre-normalize tokens once** in `addScoreForStrings` instead of re-normalizing per chunk +8. **Skip edit distance computation** when fuzzy matching is disabled +9. **Faster content snippet extraction** — regex `/<[^>]*>/g` instead of `striptags` library; normalize only the snippet window, not full content +10. **`removeDiacritic()` hoisted outside regex while-loop** in highlighting +11. **Single-token autocomplete fast path** — skips the recursive parent walk entirely, uses `getBestNotePath()` directly +12. **User option `searchEnableFuzzyMatching`** — lets users disable fuzzy matching for fastest possible search --- -## Scale Comparison Summary +## Known Limitations -Side-by-side comparison across all note counts for the most common query patterns. - -### `"meeting"` autocomplete (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.5ms | 1.6ms | **-36%** | -| 5,000 | 10.3ms | 7.6ms | **-26%** | -| 10,000 | 22.5ms | 14.4ms | **-36%** | -| 20,000 | 53.7ms | 33.2ms | **-38%** | - -### `"meeting notes"` autocomplete (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 4.6ms | 1.1ms | **-76%** | -| 5,000 | 17.5ms | 6.7ms | **-62%** | -| 10,000 | 32.7ms | 16.8ms | **-49%** | -| 20,000 | 71.6ms | 38.9ms | **-46%** | - -### `"xyznonexistent"` autocomplete (fuzzy OFF) - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 0.4ms | 0.4ms | 0% | -| 5,000 | 2.2ms | 2.3ms | +5% | -| 10,000 | 6.3ms | 8.4ms | +33% | -| 20,000 | 21.9ms | 19.3ms | **-12%** | - -### `"xyznonexistent"` fullSearch (fuzzy ON) — worst case path - -| Notes | main | feature | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 1.2ms | 1.0ms | **-17%** | -| 5,000 | 8.6ms | 8.7ms | +1% | -| 10,000 | 22.4ms | 22.2ms | -1% | -| 20,000 | 72.2ms | 64.5ms | **-11%** | - ---- - -## Summary of Improvements - -### Autocomplete (typing in search bar) — 30-70% faster: -- **Single-token** at all scales (29-42% faster) -- **Multi-token** — the biggest consistent gains (50-70% faster) -- **Typing progression** (26-49% faster per keystroke at 10K notes) -- **Diacritics queries** (28-53% faster) -- **Broad term** (e.g., `"note"` matching 8,500 results: 49% faster) - -### Full search (pressing Enter) — 25-58% faster: -- **Multi-token full search** (fuzzy OFF): 47-64% faster at all scales -- **Multi-token full search** (fuzzy ON): 45-65% faster at all scales -- **4-token full search** at 10K: 51-56% faster -- **Typo recovery** (`"projct planing"` + fuzzy): 134ms → 95ms (**-29%**) -- **Realistic queries** (`"project planning"` full search): 33ms → 19ms (**-43%**) - -### Dramatic wins (80%+ improvement): -- **Autocomplete with fuzzy ON, no-match queries** (65-92% faster — fuzzy fallback skipped entirely) -- **Autocomplete typo queries** (e.g., `"projct"` + fuzzy: 101ms → 6ms, **-94%**) - -### Where performance is roughly equal or slightly slower: -- Single-token full search at 1K notes (small dataset noise) -- No-match queries without fuzzy at smaller scales -- Full-search worst case (no-match + multi-token + fuzzy): 4-11% improvement - -### Key optimizations in this PR: -1. **Pre-built flat text index** with incremental updates in Becca -2. **Skip two-phase fuzzy fallback** for autocomplete searches -3. **Pre-normalized attribute names/values** on BAttribute -4. **Cached normalized parent titles** per search execution -5. **Set-based token lookup** in searchPathTowardsRoot (O(1) vs O(n)) -6. **Removed redundant toLowerCase()** throughout scoring pipeline -7. **Pre-normalize tokens once** in addScoreForStrings instead of per-chunk -8. **Skip edit distance** when fuzzy matching is disabled -9. **Faster content snippet extraction** — regex strip, window normalization -10. **removeDiacritic() outside regex while-loop** in highlighting -11. **Single-token autocomplete fast path** — skips recursive parent walk -12. **User option** to disable fuzzy matching entirely for fastest mode +- These benchmarks measure the **in-memory pipeline only** (titles, attributes, scoring, highlighting). The `NoteContentFulltextExp` sequential blob scan from SQLite is not exercised because `getContent()` is monkeypatched. In production, the full search path (`fastSearch=false`) includes reading every note's content from disk, which adds significant time at scale. +- Fuzzy matching on the full-search two-phase path shows slight regressions (+9-12%) for single-token queries because edit distance computation cost hasn't changed on that path. Multi-token queries still improve because the token normalization and tree walk optimizations apply to both paths. +- At 1K notes, some results show noise-level regressions. The optimizations target 5K+ note scales where overhead is measurable. From 32a2834bf350ed25e7827f49a3d97029a6a647a3 Mon Sep 17 00:00:00 2001 From: Elias Soares Date: Sat, 11 Apr 2026 14:25:49 -0300 Subject: [PATCH 024/203] fix(etapi): correct calendar year/month endpoint descriptions and year pattern; docs: add trilium-fastmcp integration --- apps/server/etapi.openapi.yaml | 8 ++++---- docs/User Guide/User Guide/AI.md | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/server/etapi.openapi.yaml b/apps/server/etapi.openapi.yaml index 8b8a65f2b3..882c069941 100644 --- a/apps/server/etapi.openapi.yaml +++ b/apps/server/etapi.openapi.yaml @@ -870,7 +870,7 @@ paths: $ref: "#/components/schemas/Error" /calendar/months/{month}: get: - description: returns a week note for a given date. Gets created if doesn't exist. + description: returns a month note for a given month. Gets created if doesn't exist. operationId: getMonthNote parameters: - name: month @@ -895,7 +895,7 @@ paths: $ref: "#/components/schemas/Error" /calendar/years/{year}: get: - description: returns a week note for a given date. Gets created if doesn't exist. + description: returns a year note for a given year. Gets created if doesn't exist. operationId: getYearNote parameters: - name: year @@ -903,8 +903,8 @@ paths: required: true schema: type: string - pattern: "[0-9]{4}-[0-9]{2}" - example: 2022-02 + pattern: "[0-9]{4}" + example: "2022" responses: "200": description: year note diff --git a/docs/User Guide/User Guide/AI.md b/docs/User Guide/User Guide/AI.md index 4ca2d07abd..8c848f1163 100644 --- a/docs/User Guide/User Guide/AI.md +++ b/docs/User Guide/User Guide/AI.md @@ -13,6 +13,7 @@ As such, there are third-party solutions that integrate an MCP server that can b * [tan-yong-sheng/triliumnext-mcp](https://github.com/tan-yong-sheng/triliumnext-mcp) * [perfectra1n/triliumnext-mcp](https://github.com/perfectra1n/triliumnext-mcp) +* [eliassoares/trilium-fastmcp](https://github.com/eliassoares/trilium-fastmcp) > [!IMPORTANT] > These solutions are third-party and thus not endorsed or supported directly by the Trilium Notes team. Please address questions and issues on their corresponding repository instead. \ No newline at end of file From e2043c14f2eb23e0ebcd51a472dd07988d1b8188 Mon Sep 17 00:00:00 2001 From: Elias Soares Date: Sat, 11 Apr 2026 20:41:08 -0300 Subject: [PATCH 025/203] improv: applying Gemini's suggestions --- apps/server/etapi.openapi.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/server/etapi.openapi.yaml b/apps/server/etapi.openapi.yaml index 882c069941..99e5a98c70 100644 --- a/apps/server/etapi.openapi.yaml +++ b/apps/server/etapi.openapi.yaml @@ -870,7 +870,8 @@ paths: $ref: "#/components/schemas/Error" /calendar/months/{month}: get: - description: returns a month note for a given month. Gets created if doesn't exist. + summary: Get a month note + description: Returns a month note for a given month (format YYYY-MM, e.g., 2022-02). The note is created if it doesn't exist. operationId: getMonthNote parameters: - name: month @@ -895,7 +896,8 @@ paths: $ref: "#/components/schemas/Error" /calendar/years/{year}: get: - description: returns a year note for a given year. Gets created if doesn't exist. + summary: Get a year note + description: Returns a year note for a given year (format YYYY, e.g., 2022). The note is created if it doesn't exist. operationId: getYearNote parameters: - name: year @@ -903,7 +905,7 @@ paths: required: true schema: type: string - pattern: "[0-9]{4}" + pattern: "^[0-9]{4}$" example: "2022" responses: "200": From 4c02d70dae8b849cac0479414090f2d15df6c69e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 00:08:40 +0300 Subject: [PATCH 026/203] fix(toc): not rendering math the first time --- apps/client/src/widgets/react/hooks.tsx | 23 +++++++++++-------- .../src/widgets/sidebar/TableOfContents.tsx | 23 ++++++++++++++++--- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 251463fa9a..6c70703385 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -1447,24 +1447,29 @@ export function useColorScheme() { export function useMathRendering(containerRef: RefObject, deps: unknown[]) { useEffect(() => { if (!containerRef.current) return; - // Support both read-only (.math-tex) and CKEditor editing view (.ck-math-tex) classes - const mathElements = containerRef.current.querySelectorAll(".math-tex, .ck-math-tex"); + const mathElements = containerRef.current.querySelectorAll(".math-tex"); for (const mathEl of mathElements) { // Skip if already rendered by KaTeX if (mathEl.querySelector(".katex")) continue; try { - let equation = mathEl.textContent || ""; + // CKEditor's data format wraps the equation with \(...\) or \[...\] + // delimiters. katex.render() expects raw LaTeX without them. + const raw = mathEl.textContent?.trim() ?? ""; + let equation: string; + let displayMode = false; - // CKEditor widgets store equation without delimiters, add them for KaTeX - if (mathEl.classList.contains("ck-math-tex")) { - // Check if it's display mode or inline - const isDisplay = mathEl.classList.contains("ck-math-tex-display"); - equation = isDisplay ? `\\[${equation}\\]` : `\\(${equation}\\)`; + if (raw.startsWith("\\(") && raw.endsWith("\\)")) { + equation = raw.slice(2, -2); + } else if (raw.startsWith("\\[") && raw.endsWith("\\]")) { + equation = raw.slice(2, -2); + displayMode = true; + } else { + equation = raw; } - math.render(equation, mathEl as HTMLElement); + math.render(equation, mathEl as HTMLElement, { displayMode }); } catch (e) { console.warn("Failed to render math:", e); } diff --git a/apps/client/src/widgets/sidebar/TableOfContents.tsx b/apps/client/src/widgets/sidebar/TableOfContents.tsx index aca1c11c5b..4442af884c 100644 --- a/apps/client/src/widgets/sidebar/TableOfContents.tsx +++ b/apps/client/src/widgets/sidebar/TableOfContents.tsx @@ -200,17 +200,34 @@ function extractTocFromTextEditor(editor: CKTextEditor) { const level = Number(item.name.replace( 'heading', '' )); - // Convert model element to view, then to DOM to get HTML + // Convert model element to view, then to DOM to get HTML. + // Math UIElements render their KaTeX content asynchronously, so + // ck-math-tex spans may be empty at read time. Replace them with + // math-tex spans (the data format) using the equation from the model, + // so useMathRendering can render them synchronously in the sidebar. const viewEl = editor.editing.mapper.toViewElement(item); let text = ''; if (viewEl) { const domEl = editor.editing.view.domConverter.mapViewToDom(viewEl); if (domEl instanceof HTMLElement) { - text = domEl.innerHTML; + const clone = domEl.cloneNode(true) as HTMLElement; + const ckMathSpans = clone.querySelectorAll('.ck-math-tex'); + let mathIdx = 0; + for (const child of item.getChildren()) { + if (!child.is('element', 'mathtex-inline')) continue; + if (mathIdx >= ckMathSpans.length) break; + const equation = String(child.getAttribute('equation') ?? ''); + const span = document.createElement('span'); + span.className = 'math-tex'; + span.textContent = `\\(${equation}\\)`; + ckMathSpans[mathIdx].replaceWith(span); + mathIdx++; + } + text = clone.innerHTML; } } - // Fallback to plain text if conversion fails + // Fallback to plain text if DOM conversion fails if (!text) { text = Array.from( item.getChildren() ) .map( c => c.is( '$text' ) ? c.data : '' ) From f9aaccdfe2dc7e5b11aea9def7f59d3b50a7bdb9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 01:06:34 +0300 Subject: [PATCH 027/203] chore(docker): set up tooling for building better-sqlite3 from scratch --- apps/server/Dockerfile | 8 ++++++++ apps/server/Dockerfile.alpine | 3 +++ apps/server/Dockerfile.alpine.rootless | 3 +++ apps/server/Dockerfile.legacy | 8 ++++++++ apps/server/Dockerfile.rootless | 8 ++++++++ 5 files changed, 30 insertions(+) diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 1db1be8182..f61f4971cc 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -1,6 +1,14 @@ FROM node:24.14.1-bullseye-slim AS builder RUN corepack enable +# Install build tools required to compile native addons (e.g. better-sqlite3) on ARM +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + make \ + g++ && \ + rm -rf /var/lib/apt/lists/* + # Install native dependencies since we might be building cross-platform. WORKDIR /usr/src/app/build COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/ diff --git a/apps/server/Dockerfile.alpine b/apps/server/Dockerfile.alpine index bf8039e224..67e2bafe5f 100644 --- a/apps/server/Dockerfile.alpine +++ b/apps/server/Dockerfile.alpine @@ -1,6 +1,9 @@ FROM node:24.14.1-alpine AS builder RUN corepack enable +# Install build tools required to compile native addons (e.g. better-sqlite3) on ARM +RUN apk add --no-cache python3 make g++ + # Install native dependencies since we might be building cross-platform. WORKDIR /usr/src/app COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/ diff --git a/apps/server/Dockerfile.alpine.rootless b/apps/server/Dockerfile.alpine.rootless index 21c0cb664c..3e4d33c555 100644 --- a/apps/server/Dockerfile.alpine.rootless +++ b/apps/server/Dockerfile.alpine.rootless @@ -1,6 +1,9 @@ FROM node:24.14.1-alpine AS builder RUN corepack enable +# Install build tools required to compile native addons (e.g. better-sqlite3) on ARM +RUN apk add --no-cache python3 make g++ + # Install native dependencies since we might be building cross-platform. WORKDIR /usr/src/app COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/ diff --git a/apps/server/Dockerfile.legacy b/apps/server/Dockerfile.legacy index ef6ddfcb06..8f514c81cd 100644 --- a/apps/server/Dockerfile.legacy +++ b/apps/server/Dockerfile.legacy @@ -1,6 +1,14 @@ FROM node:22.21.0-bullseye-slim AS builder RUN corepack enable +# Install build tools required to compile native addons (e.g. better-sqlite3) on ARM +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + make \ + g++ && \ + rm -rf /var/lib/apt/lists/* + # Install native dependencies since we might be building cross-platform. WORKDIR /usr/src/app/build COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/ diff --git a/apps/server/Dockerfile.rootless b/apps/server/Dockerfile.rootless index 5b82d2ab1d..ea4b6085ec 100644 --- a/apps/server/Dockerfile.rootless +++ b/apps/server/Dockerfile.rootless @@ -1,6 +1,14 @@ FROM node:24.14.1-bullseye-slim AS builder RUN corepack enable +# Install build tools required to compile native addons (e.g. better-sqlite3) on ARM +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + make \ + g++ && \ + rm -rf /var/lib/apt/lists/* + # Install native dependencies since we might be building cross-platform. WORKDIR /usr/src/app/build COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/ From a44ff4c78b633837dd067231fbd6dd6bdf948b02 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 01:36:07 +0000 Subject: [PATCH 028/203] chore(deps): update vitest monorepo to v4.1.4 --- apps/website/package.json | 2 +- package.json | 8 +- packages/ckeditor5-admonition/package.json | 6 +- packages/ckeditor5-footnotes/package.json | 6 +- .../ckeditor5-keyboard-marker/package.json | 6 +- packages/ckeditor5-math/package.json | 6 +- packages/ckeditor5-mermaid/package.json | 6 +- packages/splitjs/package.json | 2 +- packages/turndown-plugin-gfm/package.json | 2 +- pnpm-lock.yaml | 248 +++++++++--------- 10 files changed, 146 insertions(+), 146 deletions(-) diff --git a/apps/website/package.json b/apps/website/package.json index 0f603e930e..4e546b4ee4 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -22,7 +22,7 @@ "typescript": "6.0.2", "user-agent-data-types": "0.4.3", "vite": "8.0.7", - "vitest": "4.1.3" + "vitest": "4.1.4" }, "eslintConfig": { "extends": "preact" diff --git a/package.json b/package.json index d8f8803e56..6e8cc64a5f 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,9 @@ "@types/express": "5.0.6", "@types/js-yaml": "4.0.9", "@types/node": "24.12.2", - "@vitest/browser-webdriverio": "4.1.3", - "@vitest/coverage-v8": "4.1.3", - "@vitest/ui": "4.1.3", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", "chalk": "5.6.2", "cross-env": "10.1.0", "dpdm": "4.0.1", @@ -78,7 +78,7 @@ "typescript-eslint": "8.58.1", "vite": "8.0.7", "vite-plugin-dts": "4.5.4", - "vitest": "4.1.3" + "vitest": "4.1.4" }, "license": "AGPL-3.0-only", "author": { diff --git a/packages/ckeditor5-admonition/package.json b/packages/ckeditor5-admonition/package.json index c51e6bfec5..4fba27aa46 100644 --- a/packages/ckeditor5-admonition/package.json +++ b/packages/ckeditor5-admonition/package.json @@ -20,8 +20,8 @@ "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", - "@vitest/browser": "4.1.3", - "@vitest/coverage-istanbul": "4.1.3", + "@vitest/browser": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", "ckeditor5": "48.0.0", "eslint": "10.2.0", "eslint-config-ckeditor5": ">=9.1.0", @@ -29,7 +29,7 @@ "stylelint-config-ckeditor5": ">=9.1.0", "typescript": "6.0.2", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.3", + "vitest": "4.1.4", "webdriverio": "9.27.0" }, "peerDependencies": { diff --git a/packages/ckeditor5-footnotes/package.json b/packages/ckeditor5-footnotes/package.json index 26de370c6f..78314231e0 100644 --- a/packages/ckeditor5-footnotes/package.json +++ b/packages/ckeditor5-footnotes/package.json @@ -21,8 +21,8 @@ "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", - "@vitest/browser": "4.1.3", - "@vitest/coverage-istanbul": "4.1.3", + "@vitest/browser": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", "ckeditor5": "48.0.0", "eslint": "10.2.0", "eslint-config-ckeditor5": ">=9.1.0", @@ -30,7 +30,7 @@ "stylelint-config-ckeditor5": ">=9.1.0", "typescript": "6.0.2", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.3", + "vitest": "4.1.4", "webdriverio": "9.27.0" }, "peerDependencies": { diff --git a/packages/ckeditor5-keyboard-marker/package.json b/packages/ckeditor5-keyboard-marker/package.json index ce8e598fe3..b21c0b7fc0 100644 --- a/packages/ckeditor5-keyboard-marker/package.json +++ b/packages/ckeditor5-keyboard-marker/package.json @@ -23,8 +23,8 @@ "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", - "@vitest/browser": "4.1.3", - "@vitest/coverage-istanbul": "4.1.3", + "@vitest/browser": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", "ckeditor5": "48.0.0", "eslint": "10.2.0", "eslint-config-ckeditor5": ">=9.1.0", @@ -32,7 +32,7 @@ "stylelint-config-ckeditor5": ">=9.1.0", "typescript": "6.0.2", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.3", + "vitest": "4.1.4", "webdriverio": "9.27.0" }, "peerDependencies": { diff --git a/packages/ckeditor5-math/package.json b/packages/ckeditor5-math/package.json index 2b510a8bda..2e44c0e5cb 100644 --- a/packages/ckeditor5-math/package.json +++ b/packages/ckeditor5-math/package.json @@ -23,8 +23,8 @@ "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", - "@vitest/browser": "4.1.3", - "@vitest/coverage-istanbul": "4.1.3", + "@vitest/browser": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", "ckeditor5": "48.0.0", "eslint": "10.2.0", "eslint-config-ckeditor5": ">=9.1.0", @@ -32,7 +32,7 @@ "stylelint-config-ckeditor5": ">=9.1.0", "typescript": "6.0.2", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.3", + "vitest": "4.1.4", "webdriverio": "9.27.0" }, "peerDependencies": { diff --git a/packages/ckeditor5-mermaid/package.json b/packages/ckeditor5-mermaid/package.json index f4679f8d1b..5812e83972 100644 --- a/packages/ckeditor5-mermaid/package.json +++ b/packages/ckeditor5-mermaid/package.json @@ -23,8 +23,8 @@ "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", - "@vitest/browser": "4.1.3", - "@vitest/coverage-istanbul": "4.1.3", + "@vitest/browser": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", "ckeditor5": "48.0.0", "eslint": "10.2.0", "eslint-config-ckeditor5": ">=9.1.0", @@ -32,7 +32,7 @@ "stylelint-config-ckeditor5": ">=9.1.0", "typescript": "6.0.2", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.3", + "vitest": "4.1.4", "webdriverio": "9.27.0" }, "peerDependencies": { diff --git a/packages/splitjs/package.json b/packages/splitjs/package.json index ab77cb8bee..951d1e6e80 100644 --- a/packages/splitjs/package.json +++ b/packages/splitjs/package.json @@ -39,6 +39,6 @@ "devDependencies": { "@rollup/plugin-buble": "1.0.3", "happy-dom": "20.8.9", - "vitest": "4.1.3" + "vitest": "4.1.4" } } diff --git a/packages/turndown-plugin-gfm/package.json b/packages/turndown-plugin-gfm/package.json index ec1e5dd255..c570fea017 100644 --- a/packages/turndown-plugin-gfm/package.json +++ b/packages/turndown-plugin-gfm/package.json @@ -27,6 +27,6 @@ "devDependencies": { "happy-dom": "20.8.9", "turndown": "7.2.4", - "vitest": "4.1.3" + "vitest": "4.1.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf50a09222..a3a5442fd4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,14 +106,14 @@ importers: specifier: 24.12.2 version: 24.12.2 '@vitest/browser-webdriverio': - specifier: 4.1.3 - version: 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + specifier: 4.1.4 + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/coverage-v8': - specifier: 4.1.3 - version: 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(@vitest/browser@4.1.4)(vitest@4.1.4) '@vitest/ui': - specifier: 4.1.3 - version: 4.1.3(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(vitest@4.1.4) chalk: specifier: 5.6.2 version: 5.6.2 @@ -181,8 +181,8 @@ importers: specifier: 4.5.4 version: 4.5.4(@types/node@24.12.2)(rollup@4.60.1)(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) apps/build-docs: devDependencies: @@ -932,8 +932,8 @@ importers: specifier: 8.0.7 version: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) packages/ckeditor5: dependencies: @@ -974,11 +974,11 @@ importers: specifier: 8.58.1 version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': - specifier: 4.1.3 - version: 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': - specifier: 4.1.3 - version: 4.1.3(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(vitest@4.1.4) ckeditor5: specifier: 48.0.0 version: 48.0.0 @@ -1001,8 +1001,8 @@ importers: specifier: 2.0.0 version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1019,11 +1019,11 @@ importers: specifier: 8.58.1 version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': - specifier: 4.1.3 - version: 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': - specifier: 4.1.3 - version: 4.1.3(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(vitest@4.1.4) ckeditor5: specifier: 48.0.0 version: 48.0.0 @@ -1046,8 +1046,8 @@ importers: specifier: 2.0.0 version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1064,11 +1064,11 @@ importers: specifier: 8.58.1 version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': - specifier: 4.1.3 - version: 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': - specifier: 4.1.3 - version: 4.1.3(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(vitest@4.1.4) ckeditor5: specifier: 48.0.0 version: 48.0.0 @@ -1091,8 +1091,8 @@ importers: specifier: 2.0.0 version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1116,11 +1116,11 @@ importers: specifier: 8.58.1 version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': - specifier: 4.1.3 - version: 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': - specifier: 4.1.3 - version: 4.1.3(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(vitest@4.1.4) ckeditor5: specifier: 48.0.0 version: 48.0.0 @@ -1143,8 +1143,8 @@ importers: specifier: 2.0.0 version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1161,11 +1161,11 @@ importers: specifier: 8.58.1 version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': - specifier: 4.1.3 - version: 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': - specifier: 4.1.3 - version: 4.1.3(vitest@4.1.3) + specifier: 4.1.4 + version: 4.1.4(vitest@4.1.4) ckeditor5: specifier: 48.0.0 version: 48.0.0 @@ -1188,8 +1188,8 @@ importers: specifier: 2.0.0 version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1450,8 +1450,8 @@ importers: specifier: 20.8.9 version: 20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5) vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) packages/turndown-plugin-gfm: devDependencies: @@ -1462,8 +1462,8 @@ importers: specifier: 7.2.4 version: 7.2.4 vitest: - specifier: 4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) packages: @@ -6344,36 +6344,36 @@ packages: resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} engines: {node: '>= 20'} - '@vitest/browser-webdriverio@4.1.3': - resolution: {integrity: sha512-EeCY0116zZk5uedmwkVdW2dfpv1JEVtK1n+3WeNnyjBVWnBzDlqY1Kkf8Z3Re1qb5Nki8LgnQgQp1VT+q/ZaMQ==} + '@vitest/browser-webdriverio@4.1.4': + resolution: {integrity: sha512-AhBipo9kfFpZ11Vx+kLD6rwJ+pnp/TpYm7tA1jOh1t1DMoj4IrofW82vW9EUY4Tmx2s/aWDxPDjrYMSoyilt8w==} peerDependencies: - vitest: 4.1.3 + vitest: 4.1.4 webdriverio: '*' - '@vitest/browser@4.1.3': - resolution: {integrity: sha512-CS9KjO2vijuBlbwz0JIgC0YuoI1BuqWI5ziD3Nll6jkpNYtWdjPMVgGynQ9vZovjsECeUqEeNjWrypP414d0CQ==} + '@vitest/browser@4.1.4': + resolution: {integrity: sha512-TrNaY/yVOwxtrxNsDUC/wQ56xSwplpytTeRAqF/197xV/ZddxxulBsxR6TrhVMyniJmp9in8d5u0AcDaNRY30w==} peerDependencies: - vitest: 4.1.3 + vitest: 4.1.4 - '@vitest/coverage-istanbul@4.1.3': - resolution: {integrity: sha512-IjlvIg2MaFDgeYOXgqxWwTh8c8Y8HkR/36SN0Iq5XtmsmbEau7a5i7g1F+Lv7G9R+vDzOt+HyNOmKqg/8kKzug==} + '@vitest/coverage-istanbul@4.1.4': + resolution: {integrity: sha512-Pyi4F8RnqU6hBGiIDhS/e8gVD4FRcUvZJ2AbFiIlmIxHlEIsKyCxGOqufCECobty/dXELcN8oIH4Gms3hVOCYA==} peerDependencies: - vitest: 4.1.3 + vitest: 4.1.4 - '@vitest/coverage-v8@4.1.3': - resolution: {integrity: sha512-/MBdrkA8t6hbdCWFKs09dPik774xvs4Z6L4bycdCxYNLHM8oZuRyosumQMG19LUlBsB6GeVpL1q4kFFazvyKGA==} + '@vitest/coverage-v8@4.1.4': + resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} peerDependencies: - '@vitest/browser': 4.1.3 - vitest: 4.1.3 + '@vitest/browser': 4.1.4 + vitest: 4.1.4 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.3': - resolution: {integrity: sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==} + '@vitest/expect@4.1.4': + resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} - '@vitest/mocker@4.1.3': - resolution: {integrity: sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==} + '@vitest/mocker@4.1.4': + resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} peerDependencies: msw: ^2.4.9 vite: '>=7.3.2' @@ -6383,25 +6383,25 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.3': - resolution: {integrity: sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==} + '@vitest/pretty-format@4.1.4': + resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} - '@vitest/runner@4.1.3': - resolution: {integrity: sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==} + '@vitest/runner@4.1.4': + resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} - '@vitest/snapshot@4.1.3': - resolution: {integrity: sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==} + '@vitest/snapshot@4.1.4': + resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} - '@vitest/spy@4.1.3': - resolution: {integrity: sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==} + '@vitest/spy@4.1.4': + resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} - '@vitest/ui@4.1.3': - resolution: {integrity: sha512-xBPy+43o1fgMLUDlufUXh7tlT/Es8uS5eiyBY2PyPfFYSGpApZskLw65DROoDz+rgYkPuAmb20Mv9Z9g1WQE7w==} + '@vitest/ui@4.1.4': + resolution: {integrity: sha512-EgFR7nlj5iTDYZYCvavjFokNYwr3c3ry0sFiCg+N7B233Nwp+NNx7eoF/XvMWDCKY71xXAG3kFkt97ZHBJVL8A==} peerDependencies: - vitest: 4.1.3 + vitest: 4.1.4 - '@vitest/utils@4.1.3': - resolution: {integrity: sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==} + '@vitest/utils@4.1.4': + resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} '@volar/language-core@2.4.13': resolution: {integrity: sha512-MnQJ7eKchJx5Oz+YdbqyFUk8BN6jasdJv31n/7r6/WwlOOv7qzvot6B66887l2ST3bUW4Mewml54euzpJWA6bg==} @@ -13626,20 +13626,20 @@ packages: yaml: optional: true - vitest@4.1.3: - resolution: {integrity: sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==} + vitest@4.1.4: + resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.3 - '@vitest/browser-preview': 4.1.3 - '@vitest/browser-webdriverio': 4.1.3 - '@vitest/coverage-istanbul': 4.1.3 - '@vitest/coverage-v8': 4.1.3 - '@vitest/ui': 4.1.3 + '@vitest/browser-playwright': 4.1.4 + '@vitest/browser-preview': 4.1.4 + '@vitest/browser-webdriverio': 4.1.4 + '@vitest/coverage-istanbul': 4.1.4 + '@vitest/coverage-v8': 4.1.4 + '@vitest/ui': 4.1.4 happy-dom: '*' jsdom: '*' vite: '>=7.3.2' @@ -20930,10 +20930,10 @@ snapshots: '@vercel/oidc@3.1.0': {} - '@vitest/browser-webdriverio@4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': + '@vitest/browser-webdriverio@4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: - '@vitest/browser': 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3) - vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/browser': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -20941,16 +20941,16 @@ snapshots: - utf-8-validate - vite - '@vitest/browser@4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3)': + '@vitest/browser@4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.3(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/utils': 4.1.3 + '@vitest/mocker': 4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/utils': 4.1.4 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -20958,7 +20958,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-istanbul@4.1.3(vitest@4.1.3)': + '@vitest/coverage-istanbul@4.1.4(vitest@4.1.4)': dependencies: '@babel/core': 7.29.0 '@istanbuljs/schema': 0.1.3 @@ -20970,14 +20970,14 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 tinyrainbow: 3.1.0 - vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.1.3(@vitest/browser@4.1.3)(vitest@4.1.3)': + '@vitest/coverage-v8@4.1.4(@vitest/browser@4.1.4)(vitest@4.1.4)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.3 + '@vitest/utils': 4.1.4 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -20986,60 +20986,60 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: - '@vitest/browser': 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3) + '@vitest/browser': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) - '@vitest/expect@4.1.3': + '@vitest/expect@4.1.4': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.2 - '@vitest/spy': 4.1.3 - '@vitest/utils': 4.1.3 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.3(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.3 + '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.7.5(@types/node@24.12.2)(typescript@6.0.2) vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/pretty-format@4.1.3': + '@vitest/pretty-format@4.1.4': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.3': + '@vitest/runner@4.1.4': dependencies: - '@vitest/utils': 4.1.3 + '@vitest/utils': 4.1.4 pathe: 2.0.3 - '@vitest/snapshot@4.1.3': + '@vitest/snapshot@4.1.4': dependencies: - '@vitest/pretty-format': 4.1.3 - '@vitest/utils': 4.1.3 + '@vitest/pretty-format': 4.1.4 + '@vitest/utils': 4.1.4 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.3': {} + '@vitest/spy@4.1.4': {} - '@vitest/ui@4.1.3(vitest@4.1.3)': + '@vitest/ui@4.1.4(vitest@4.1.4)': dependencies: - '@vitest/utils': 4.1.3 + '@vitest/utils': 4.1.4 fflate: 0.8.2 flatted: 3.4.2 pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/utils@4.1.3': + '@vitest/utils@4.1.4': dependencies: - '@vitest/pretty-format': 4.1.3 + '@vitest/pretty-format': 4.1.4 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -29651,15 +29651,15 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vitest@4.1.3(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.3)(@vitest/coverage-istanbul@4.1.3)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.3 - '@vitest/mocker': 4.1.3(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.3 - '@vitest/runner': 4.1.3 - '@vitest/snapshot': 4.1.3 - '@vitest/spy': 4.1.3 - '@vitest/utils': 4.1.3 + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -29676,10 +29676,10 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 24.12.2 - '@vitest/browser-webdriverio': 4.1.3(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.3)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) - '@vitest/coverage-istanbul': 4.1.3(vitest@4.1.3) - '@vitest/coverage-v8': 4.1.3(@vitest/browser@4.1.3)(vitest@4.1.3) - '@vitest/ui': 4.1.3(vitest@4.1.3) + '@vitest/browser-webdriverio': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + '@vitest/coverage-istanbul': 4.1.4(vitest@4.1.4) + '@vitest/coverage-v8': 4.1.4(@vitest/browser@4.1.4)(vitest@4.1.4) + '@vitest/ui': 4.1.4(vitest@4.1.4) happy-dom: 20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5) jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: From 2411d7bc76c500ade010009cd5c626bba91eb652 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 01:36:47 +0000 Subject: [PATCH 029/203] fix(deps): update ai sdk --- apps/server/package.json | 4 ++-- pnpm-lock.yaml | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index ab31391524..1570db2e0c 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -31,10 +31,10 @@ }, "dependencies": { "@ai-sdk/anthropic": "3.0.68", - "@ai-sdk/google": "3.0.60", + "@ai-sdk/google": "3.0.61", "@ai-sdk/openai": "3.0.52", "@modelcontextprotocol/sdk": "^1.12.1", - "ai": "6.0.154", + "ai": "6.0.156", "better-sqlite3": "12.8.0", "html-to-text": "9.0.5", "js-yaml": "4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf50a09222..5ed19204ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -572,8 +572,8 @@ importers: specifier: 3.0.68 version: 3.0.68(zod@4.3.6) '@ai-sdk/google': - specifier: 3.0.60 - version: 3.0.60(zod@4.3.6) + specifier: 3.0.61 + version: 3.0.61(zod@4.3.6) '@ai-sdk/openai': specifier: 3.0.52 version: 3.0.52(zod@4.3.6) @@ -581,8 +581,8 @@ importers: specifier: ^1.12.1 version: 1.29.0(zod@4.3.6) ai: - specifier: 6.0.154 - version: 6.0.154(zod@4.3.6) + specifier: 6.0.156 + version: 6.0.156(zod@4.3.6) better-sqlite3: specifier: 12.8.0 version: 12.8.0 @@ -1479,14 +1479,14 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/gateway@3.0.94': - resolution: {integrity: sha512-uDDwLZhCkvC89crVS3S90D5L7AcVN8WriGuYVNYgVAaVcvy3Mthy3R9ICfzG75BObhz6pm2FWnhxDfNRK+t69Q==} + '@ai-sdk/gateway@3.0.95': + resolution: {integrity: sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/google@3.0.60': - resolution: {integrity: sha512-ye/hG0LeO24VmjLbfgkFZV8V8k/l4nVBODutpJQkFPyUiGOCbFtFUTgxSeC7+njrk5+HhgyHrzJay4zmhwMH+w==} + '@ai-sdk/google@3.0.61': + resolution: {integrity: sha512-jEKU1Mjcy5CoicejdJQIzM0ntYwyXR8vtYgAZYriKaOuLAiAhiiU538++fGU3CC9HJH/mL1OfsCwMM3gFiCNsw==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -6630,8 +6630,8 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ai@6.0.154: - resolution: {integrity: sha512-HfKJKCTJsDZxqrIUDSVnBQ7DpQlx5WI4ExqtLd7Bl70epLmvkpc/HYMzU1hP9W+g9VEAcvZo4fbMqc3v5D+9gQ==} + ai@6.0.156: + resolution: {integrity: sha512-uyi/5LYbugHQxZsR2PeAFOZEL4WqKkzZw4pv0nQvvdgxgVOsM7snOmGrYkp5fShxH/vnd08SXvHCVTX7oUW7xQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -14107,14 +14107,14 @@ snapshots: '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/gateway@3.0.94(zod@4.3.6)': + '@ai-sdk/gateway@3.0.95(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) '@vercel/oidc': 3.1.0 zod: 4.3.6 - '@ai-sdk/google@3.0.60(zod@4.3.6)': + '@ai-sdk/google@3.0.61(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) @@ -21322,9 +21322,9 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ai@6.0.154(zod@4.3.6): + ai@6.0.156(zod@4.3.6): dependencies: - '@ai-sdk/gateway': 3.0.94(zod@4.3.6) + '@ai-sdk/gateway': 3.0.95(zod@4.3.6) '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) '@opentelemetry/api': 1.9.0 From c1596d2a2532d6ffb924b610d55f3a76f7055191 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 01:37:28 +0000 Subject: [PATCH 030/203] chore(deps): update ckeditor5 config packages to v14.1.0 --- pnpm-lock.yaml | 374 ++++++++++--------------------------------------- 1 file changed, 71 insertions(+), 303 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf50a09222..3119cab301 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -987,13 +987,13 @@ importers: version: 10.2.0(jiti@2.6.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 14.1.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) stylelint: specifier: 17.6.0 version: 17.6.0(typescript@6.0.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.6.0(typescript@6.0.2)) + version: 14.1.0(stylelint@17.6.0(typescript@6.0.2)) typescript: specifier: 6.0.2 version: 6.0.2 @@ -1032,13 +1032,13 @@ importers: version: 10.2.0(jiti@2.6.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 14.1.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) stylelint: specifier: 17.6.0 version: 17.6.0(typescript@6.0.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.6.0(typescript@6.0.2)) + version: 14.1.0(stylelint@17.6.0(typescript@6.0.2)) typescript: specifier: 6.0.2 version: 6.0.2 @@ -1077,13 +1077,13 @@ importers: version: 10.2.0(jiti@2.6.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 14.1.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) stylelint: specifier: 17.6.0 version: 17.6.0(typescript@6.0.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.6.0(typescript@6.0.2)) + version: 14.1.0(stylelint@17.6.0(typescript@6.0.2)) typescript: specifier: 6.0.2 version: 6.0.2 @@ -1129,13 +1129,13 @@ importers: version: 10.2.0(jiti@2.6.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 14.1.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) stylelint: specifier: 17.6.0 version: 17.6.0(typescript@6.0.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.6.0(typescript@6.0.2)) + version: 14.1.0(stylelint@17.6.0(typescript@6.0.2)) typescript: specifier: 6.0.2 version: 6.0.2 @@ -1174,13 +1174,13 @@ importers: version: 10.2.0(jiti@2.6.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + version: 14.1.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) stylelint: specifier: 17.6.0 version: 17.6.0(typescript@6.0.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.6.0(typescript@6.0.2)) + version: 14.1.0(stylelint@17.6.0(typescript@6.0.2)) typescript: specifier: 6.0.2 version: 6.0.2 @@ -3680,8 +3680,8 @@ packages: '@mapbox/point-geometry@0.1.0': resolution: {integrity: sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==} - '@mapbox/tiny-sdf@2.0.7': - resolution: {integrity: sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug==} + '@mapbox/tiny-sdf@2.1.0': + resolution: {integrity: sha512-uFJhNh36BR4OCuWIEiWaEix9CA2WzT6CAIcqVjWYpnx8+QDtS+oC4QehRrx5cX4mgWs37MmKnwUejeHxVymzNg==} '@mapbox/unitbezier@0.0.1': resolution: {integrity: sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==} @@ -5362,8 +5362,8 @@ packages: '@types/debounce@1.2.4': resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -5628,14 +5628,6 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.57.1': - resolution: {integrity: sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.57.1 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/eslint-plugin@8.58.1': resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5644,13 +5636,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.57.1': - resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.58.1': resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5658,61 +5643,22 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.57.1': - resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/project-service@8.58.0': - resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.58.1': resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.57.1': - resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/scope-manager@8.58.0': - resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.58.1': resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.57.1': - resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/tsconfig-utils@8.58.0': - resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/tsconfig-utils@8.58.1': resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.57.1': - resolution: {integrity: sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.58.1': resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5720,50 +5666,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.57.1': - resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/types@8.58.0': - resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.58.1': resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.57.1': - resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/typescript-estree@8.58.0': - resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@8.58.1': resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.57.1': - resolution: {integrity: sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - - '@typescript-eslint/utils@8.58.0': - resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.58.1': resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5771,14 +5683,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.57.1': - resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/visitor-keys@8.58.0': - resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.58.1': resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8365,8 +8269,8 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-config-ckeditor5@14.0.0: - resolution: {integrity: sha512-MM/1u0PAFMWD878H3NZ5oSNppWrwOO4WuWyFFr1oyEKtnMT/3q9JK47XeHm2IKoq9SIaQFcI47cmQIffxnXKGg==} + eslint-config-ckeditor5@14.1.0: + resolution: {integrity: sha512-mDDu0YB/GnoIXbXKCldUaEsC+Z/5lZVrHetEvpP8MI0zuWChbNBrPIokeQ829Yz7uSF7PEk8bFPXktwAK2ccQA==} engines: {node: '>=24.11.0'} peerDependencies: eslint: ^9.0.0 @@ -8386,8 +8290,8 @@ packages: eslint-linter-browserify@10.2.0: resolution: {integrity: sha512-3EI7WAce/YTRTyRmqYUKX7VRROyIoKacg3625Ler+079JvCU5YGQwVJLa/ifvBxr95bxv7gTsy4zw3is+hHpBg==} - eslint-plugin-ckeditor5-rules@14.0.0: - resolution: {integrity: sha512-TLdFs+qhiEzVO4LQgVrJmc/49ZQ62qKq0fx+M+231tzGXSqioRV6LpRLHvF7XxEuG3+3LblMUJTLM+pBm5fXMg==} + eslint-plugin-ckeditor5-rules@14.1.0: + resolution: {integrity: sha512-ssewEesRNL0MVfNTraM7l8xtTfBKmlNIjK0kCXsiJgLanmoRn0CZ1LEepICZifNx2eKzCf5tF+6phYVleh88PA==} engines: {node: '>=24.11.0'} eslint-plugin-compat@6.0.2: @@ -9092,8 +8996,8 @@ packages: grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - graphql@16.13.1: - resolution: {integrity: sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ==} + graphql@16.13.2: + resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} growly@1.3.0: @@ -10665,6 +10569,10 @@ packages: resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} engines: {node: '>= 8'} + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + minipass-pipeline@1.2.4: resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} engines: {node: '>=8'} @@ -11516,6 +11424,10 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.9: + resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} + engines: {node: ^10 || ^12 || >=14} + postject@1.0.0-alpha.6: resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==} engines: {node: '>=14.0.0'} @@ -11615,8 +11527,8 @@ packages: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} - protocol-buffers-schema@3.6.0: - resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==} + protocol-buffers-schema@3.6.1: + resolution: {integrity: sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==} proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} @@ -12834,8 +12746,8 @@ packages: react-dom: optional: true - stylelint-config-ckeditor5@14.0.0: - resolution: {integrity: sha512-RrT7Zc2siW6bvbG22Svfi3/tcqhTCuxeJ0BgwCpD67bDTmysK7mdM422FrrF03HVa35RJ78aeHsSWqQxQE+KQA==} + stylelint-config-ckeditor5@14.1.0: + resolution: {integrity: sha512-/fGcg0+G9WrsaQoFYQdd+4cYmbE6TqB8xWQOJOv5v/AeThdYPAqQmnsks2SlFD8ulCInVn7r7shz8KeOG8hpMA==} engines: {node: '>=24.11.0'} peerDependencies: stylelint: '>=16.0.0' @@ -12846,8 +12758,8 @@ packages: peerDependencies: stylelint: ^16.16.0 - stylelint-plugin-ckeditor5-rules@14.0.0: - resolution: {integrity: sha512-Hiv4qbntPHf/MDXMk6MpWJOiQfsgwl6W5Ema74DyydJfxKd3IaEL3fFZmGAe0EOT+vrC8i2cVOdgMg/+1jqjGA==} + stylelint-plugin-ckeditor5-rules@14.1.0: + resolution: {integrity: sha512-Y0rm/5rf0VCbp5o4aLGboclVnBj4E6nIHHcIG8ji9BPC+bh+K9KMxjlbN3bl6wRN7RpHqpEEiWaPGmBPY5jIkw==} engines: {node: '>=24.11.0'} peerDependencies: stylelint: '>=16.0.0' @@ -12944,6 +12856,10 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + tar-fs@3.1.1: resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} @@ -13212,13 +13128,6 @@ packages: peerDependencies: typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x - typescript-eslint@8.57.1: - resolution: {integrity: sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' - typescript-eslint@8.58.1: resolution: {integrity: sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -16021,7 +15930,7 @@ snapshots: '@es-joy/jsdoccomment@0.50.2': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.58.0 + '@typescript-eslint/types': 8.58.1 comment-parser: 1.4.1 esquery: 1.7.0 jsdoc-type-pratt-parser: 4.1.0 @@ -17266,7 +17175,7 @@ snapshots: '@mapbox/point-geometry@0.1.0': {} - '@mapbox/tiny-sdf@2.0.7': {} + '@mapbox/tiny-sdf@2.1.0': {} '@mapbox/unitbezier@0.0.1': {} @@ -18743,7 +18652,7 @@ snapshots: '@stylistic/eslint-plugin@4.4.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: - '@typescript-eslint/utils': 8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) eslint: 10.2.0(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -18759,7 +18668,7 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 '@csstools/media-query-list-parser': 3.0.1(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) is-plain-object: 5.0.0 - postcss: 8.5.8 + postcss: 8.5.9 postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 style-search: 0.1.0 @@ -18977,7 +18886,7 @@ snapshots: '@types/debounce@1.2.4': {} - '@types/debug@4.1.12': + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -19272,22 +19181,6 @@ snapshots: '@types/node': 24.12.2 optional: true - '@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/type-utils': 8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/utils': 8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.57.1 - eslint: 10.2.0(jiti@2.6.1) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -19304,18 +19197,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@6.0.2) - '@typescript-eslint/visitor-keys': 8.57.1 - debug: 4.4.3 - eslint: 10.2.0(jiti@2.6.1) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/scope-manager': 8.58.1 @@ -19328,24 +19209,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.57.1(typescript@6.0.2)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@6.0.2) - '@typescript-eslint/types': 8.58.0 - debug: 4.4.3 - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/project-service@8.58.0(typescript@6.0.2)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@6.0.2) - '@typescript-eslint/types': 8.58.0 - debug: 4.4.3 - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/project-service@8.58.1(typescript@6.0.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@6.0.2) @@ -19355,45 +19218,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.57.1': - dependencies: - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/visitor-keys': 8.57.1 - - '@typescript-eslint/scope-manager@8.58.0': - dependencies: - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/visitor-keys': 8.58.0 - '@typescript-eslint/scope-manager@8.58.1': dependencies: '@typescript-eslint/types': 8.58.1 '@typescript-eslint/visitor-keys': 8.58.1 - '@typescript-eslint/tsconfig-utils@8.57.1(typescript@6.0.2)': - dependencies: - typescript: 6.0.2 - - '@typescript-eslint/tsconfig-utils@8.58.0(typescript@6.0.2)': - dependencies: - typescript: 6.0.2 - '@typescript-eslint/tsconfig-utils@8.58.1(typescript@6.0.2)': dependencies: typescript: 6.0.2 - '@typescript-eslint/type-utils@8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - debug: 4.4.3 - eslint: 10.2.0(jiti@2.6.1) - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/type-utils@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/types': 8.58.1 @@ -19406,42 +19239,8 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.57.1': {} - - '@typescript-eslint/types@8.58.0': {} - '@typescript-eslint/types@8.58.1': {} - '@typescript-eslint/typescript-estree@8.57.1(typescript@6.0.2)': - dependencies: - '@typescript-eslint/project-service': 8.57.1(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@6.0.2) - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/visitor-keys': 8.57.1 - debug: 4.4.3 - minimatch: 10.2.4 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@8.58.0(typescript@6.0.2)': - dependencies: - '@typescript-eslint/project-service': 8.58.0(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@6.0.2) - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/visitor-keys': 8.58.0 - debug: 4.4.3 - minimatch: 10.2.4 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.58.1(typescript@6.0.2)': dependencies: '@typescript-eslint/project-service': 8.58.1(typescript@6.0.2) @@ -19457,28 +19256,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.57.1 - '@typescript-eslint/types': 8.57.1 - '@typescript-eslint/typescript-estree': 8.57.1(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.58.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.58.0 - '@typescript-eslint/types': 8.58.0 - '@typescript-eslint/typescript-estree': 8.58.0(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.0(jiti@2.6.1)) @@ -19490,16 +19267,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.57.1': - dependencies: - '@typescript-eslint/types': 8.57.1 - eslint-visitor-keys: 5.0.1 - - '@typescript-eslint/visitor-keys@8.58.0': - dependencies: - '@typescript-eslint/types': 8.58.0 - eslint-visitor-keys: 5.0.1 - '@typescript-eslint/visitor-keys@8.58.1': dependencies: '@typescript-eslint/types': 8.58.1 @@ -21853,7 +21620,7 @@ snapshots: lru-cache: 7.18.3 minipass: 3.3.6 minipass-collect: 1.0.2 - minipass-flush: 1.0.5 + minipass-flush: 1.0.7 minipass-pipeline: 1.2.4 mkdirp: 1.0.4 p-map: 4.0.0 @@ -23226,7 +22993,7 @@ snapshots: enhanced-resolve@5.20.1: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.2 entities@2.2.0: {} @@ -23527,17 +23294,17 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-ckeditor5@14.0.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): + eslint-config-ckeditor5@14.1.0(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): dependencies: '@eslint/js': 9.39.4 '@eslint/markdown': 6.6.0 '@stylistic/eslint-plugin': 4.4.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) eslint: 10.2.0(jiti@2.6.1) - eslint-plugin-ckeditor5-rules: 14.0.0 + eslint-plugin-ckeditor5-rules: 14.1.0 eslint-plugin-mocha: 11.2.0(eslint@10.2.0(jiti@2.6.1)) globals: 16.5.0 typescript: 6.0.2 - typescript-eslint: 8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) + typescript-eslint: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) transitivePeerDependencies: - supports-color @@ -23562,7 +23329,7 @@ snapshots: eslint-linter-browserify@10.2.0: {} - eslint-plugin-ckeditor5-rules@14.0.0: + eslint-plugin-ckeditor5-rules@14.1.0: dependencies: '@es-joy/jsdoccomment': 0.50.2 enhanced-resolve: 5.20.1 @@ -24424,7 +24191,7 @@ snapshots: grapheme-splitter@1.0.4: {} - graphql@16.13.1: + graphql@16.13.2: optional: true growly@1.3.0: {} @@ -25731,7 +25498,7 @@ snapshots: minipass: 3.3.6 minipass-collect: 1.0.2 minipass-fetch: 2.1.2 - minipass-flush: 1.0.5 + minipass-flush: 1.0.7 minipass-pipeline: 1.2.4 negotiator: 0.6.4 promise-retry: 2.0.1 @@ -25785,7 +25552,7 @@ snapshots: '@mapbox/geojson-rewind': 0.5.2 '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/point-geometry': 0.1.0 - '@mapbox/tiny-sdf': 2.0.7 + '@mapbox/tiny-sdf': 2.1.0 '@mapbox/unitbezier': 0.0.1 '@mapbox/vector-tile': 1.3.1 '@mapbox/whoots-js': 3.1.0 @@ -26201,7 +25968,7 @@ snapshots: micromark@4.0.2: dependencies: - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3 decode-named-character-reference: 1.3.0 devlop: 1.1.0 @@ -26309,6 +26076,10 @@ snapshots: dependencies: minipass: 3.3.6 + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + minipass-pipeline@1.2.4: dependencies: minipass: 3.3.6 @@ -26397,7 +26168,7 @@ snapshots: '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 '@types/statuses': 2.0.6 - graphql: 16.13.1 + graphql: 16.13.2 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -27214,6 +26985,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.9: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postject@1.0.0-alpha.6: dependencies: commander: 9.5.0 @@ -27310,7 +27087,7 @@ snapshots: '@types/node': 24.12.2 long: 5.3.2 - protocol-buffers-schema@3.6.0: {} + protocol-buffers-schema@3.6.1: {} proxy-addr@2.0.7: dependencies: @@ -27790,7 +27567,7 @@ snapshots: resolve-protobuf-schema@2.1.0: dependencies: - protocol-buffers-schema: 3.6.0 + protocol-buffers-schema: 3.6.1 resolve.exports@2.0.3: {} @@ -28743,18 +28520,18 @@ snapshots: optionalDependencies: react-dom: 19.2.4(react@19.2.4) - stylelint-config-ckeditor5@14.0.0(stylelint@17.6.0(typescript@6.0.2)): + stylelint-config-ckeditor5@14.1.0(stylelint@17.6.0(typescript@6.0.2)): dependencies: '@stylistic/stylelint-plugin': 3.1.3(stylelint@17.6.0(typescript@6.0.2)) stylelint: 17.6.0(typescript@6.0.2) stylelint-config-recommended: 16.0.0(stylelint@17.6.0(typescript@6.0.2)) - stylelint-plugin-ckeditor5-rules: 14.0.0(stylelint@17.6.0(typescript@6.0.2)) + stylelint-plugin-ckeditor5-rules: 14.1.0(stylelint@17.6.0(typescript@6.0.2)) stylelint-config-recommended@16.0.0(stylelint@17.6.0(typescript@6.0.2)): dependencies: stylelint: 17.6.0(typescript@6.0.2) - stylelint-plugin-ckeditor5-rules@14.0.0(stylelint@17.6.0(typescript@6.0.2)): + stylelint-plugin-ckeditor5-rules@14.1.0(stylelint@17.6.0(typescript@6.0.2)): dependencies: stylelint: 17.6.0(typescript@6.0.2) @@ -28920,6 +28697,8 @@ snapshots: tapable@2.3.0: {} + tapable@2.3.2: {} + tar-fs@3.1.1: dependencies: pump: 3.0.3 @@ -29219,17 +28998,6 @@ snapshots: typescript: 6.0.2 yaml: 2.8.3 - typescript-eslint@8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): - dependencies: - '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/parser': 8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - '@typescript-eslint/typescript-estree': 8.57.1(typescript@6.0.2) - '@typescript-eslint/utils': 8.57.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) - eslint: 10.2.0(jiti@2.6.1) - typescript: 6.0.2 - transitivePeerDependencies: - - supports-color - typescript-eslint@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2): dependencies: '@typescript-eslint/eslint-plugin': 8.58.1(@typescript-eslint/parser@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) From 7e30b8c02939610becbe869e412dce92bf7eb79e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 01:37:35 +0000 Subject: [PATCH 031/203] chore(deps): update actions/github-script action to v9 --- .github/actions/deploy-to-cloudflare-pages/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/deploy-to-cloudflare-pages/action.yml b/.github/actions/deploy-to-cloudflare-pages/action.yml index 4f7a4a177a..a3a22f9fef 100644 --- a/.github/actions/deploy-to-cloudflare-pages/action.yml +++ b/.github/actions/deploy-to-cloudflare-pages/action.yml @@ -55,7 +55,7 @@ runs: # Post deployment URL as PR comment - name: Comment PR with Preview URL if: github.event_name == 'pull_request' - uses: actions/github-script@v8 + uses: actions/github-script@v9 env: COMMENT_BODY: ${{ inputs.comment_body }} PRODUCTION_URL: ${{ inputs.production_url }} From b67bd018584e7f2e654f21e6663ca2b3564948da Mon Sep 17 00:00:00 2001 From: noobhjy Date: Sun, 12 Apr 2026 10:52:56 +0200 Subject: [PATCH 032/203] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 97.5% (1818 of 1864 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- apps/client/src/translations/cn/translation.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index 6d2b66ebd3..4a95006b9c 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -95,7 +95,8 @@ "notes_to_be_deleted": "将删除以下笔记 ({{notesCount}})", "no_note_to_delete": "没有笔记将被删除(仅克隆)。", "broken_relations_to_be_deleted": "将删除以下关系并断开连接 ({{ relationCount}})", - "cancel": "取消" + "cancel": "取消", + "title": "删除笔记" }, "export": { "export_note_title": "导出笔记", From 224c31d16b57b1d9b12dea887f99db973dea8e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Sun, 12 Apr 2026 14:26:12 +0200 Subject: [PATCH 033/203] Translated using Weblate (Irish) Currently translated at 100.0% (402 of 402 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ga/ --- apps/server/src/assets/translations/ga/server.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/server/src/assets/translations/ga/server.json b/apps/server/src/assets/translations/ga/server.json index c03d9fad11..00e9c2ef9f 100644 --- a/apps/server/src/assets/translations/ga/server.json +++ b/apps/server/src/assets/translations/ga/server.json @@ -445,5 +445,18 @@ }, "desktop": { "instance_already_running": "Tá sampla ag rith cheana féin, agus tá fócas á chur ar an sampla sin ina ionad." + }, + "script": { + "wrong-environment": "Ní féidir an nóta \"{{- noteTitle}}\" ({{- noteId}}) a fhorghníomhú. Is script {{- actualEnv}} é seo, ach rinneadh iarracht é a fhorghníomhú sa {{- expectedEnv}}." + }, + "search": { + "error": { + "in-context": "Earráid i {{- context}}: {{- message}}", + "reserved-keyword": "Is eochairfhocal curtha in áirithe é \"{{- token}}\". Chun luach liteartha a chuardach, bain úsáid as comharthaí athfhriotail: \"{{- token}}\"", + "cannot-compare-with": "ní féidir comparáid a dhéanamh le \"{{- token}}\". Chun luach liteartha a chuardach, bain úsáid as comharthaí athfhriotail: \"{{- token}}\"", + "misplaced-expression": "Slonn mí-áitithe nó neamhiomlán \"{{- token}}\"", + "fulltext-after-expression": "Ní abairt bhailí í \"{{- token}}\". Chun téacs a chuardach, cuir é roimh scagairí tréithe (m.sh., \"{{- token}} #label\" in ionad \"#label {{- token}}\").", + "unrecognized-expression": "Sloinneadh neamhaitheanta \"{{- token}}\"" + } } } From d41d7279509c197ca9c89a17d25704ad8d1c7f56 Mon Sep 17 00:00:00 2001 From: green Date: Sun, 12 Apr 2026 04:48:01 +0200 Subject: [PATCH 034/203] Translated using Weblate (Japanese) Currently translated at 99.9% (1863 of 1864 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- .../src/translations/ja/translation.json | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index e808ec0118..8a6e83c721 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -106,13 +106,21 @@ }, "delete_notes": { "delete_all_clones_description": "すべてのクローンも削除(最近の変更では元に戻すことができる)", - "erase_notes_description": "通常の(ソフト)削除では、ノートは削除されたものとしてマークされ、一定期間内に(最近の変更で)削除を取り消すことができます。このオプションをオンにすると、ノートは即座に削除され、削除を取り消すことはできません。", + "erase_notes_description": "ノートを一時保存せずに完全に削除します。この操作は元に戻すことができず、アプリケーションが再読み込みされます。", "erase_notes_warning": "すべてのクローンを含め、ノートを完全に消去します(元に戻せません)。これにより、アプリケーションは強制的にリロードされます。", - "notes_to_be_deleted": "以下のノートが削除されます ({{notesCount}})", + "notes_to_be_deleted": "削除対象のノート ({{notesCount}})", "no_note_to_delete": "ノートは削除されません(クローンのみ)。", "cancel": "キャンセル", "close": "閉じる", - "broken_relations_to_be_deleted": "次のリレーション ({{relationCount}})は壊れているので消去されます" + "broken_relations_to_be_deleted": "壊れたリレーション ({{relationCount}})", + "title": "ノートを削除", + "clones_label": "クローン", + "delete_clones_description_other": "他の {{count}} 件のクローンも削除します。最近の変更履歴から元に戻すことができます。", + "erase_notes_label": "完全に消去", + "table_note_with_relation": "リレーションがあるノート", + "table_relation": "リレーション", + "table_points_to": "参照先 (削除済み)", + "delete": "削除" }, "calendar": { "mon": "月", @@ -809,7 +817,8 @@ "label_note": "ノート", "box_size_small": "スモール (~ 10 行)", "box_size_medium": "ミディアム (~ 30 行)", - "box_size_full": "フル (ボックスに全文が表示されます)" + "box_size_full": "フル (ボックスに全文が表示されます)", + "box_size_expandable": "展開可能 (デフォルトでは折りたたまれています)" }, "ancestor": { "placeholder": "ノート名で検索", @@ -1354,7 +1363,8 @@ "theme_none": "シンタックスハイライトなし", "theme_group_light": "ライトテーマ", "theme_group_dark": "ダークテーマ", - "copy_title": "クリップボードにコピー" + "copy_title": "クリップボードにコピー", + "click_to_copy": "クリックしてコピー" }, "editor": { "title": "エディター" @@ -2274,7 +2284,8 @@ "note_context_disabled": "クリックして現在のノートをコンテキストに含める", "no_provider_message": "AI プロバイダーが設定されていません。チャットを開始するには、プロバイダーを追加してください。", "add_provider": "AI プロバイダーを追加", - "sources_summary": "{{count}} 件のソースを {{sites}} サイトから取得" + "sources_summary": "{{count}} 件のソースを {{sites}} サイトから取得", + "stop": "停止" }, "sidebar_chat": { "title": "AI チャット", @@ -2338,7 +2349,11 @@ "web_search": "Web 検索", "note_in_parent": "", "get_attachment": "添付ファイルを取得", - "get_attachment_content": "添付ファイルの内容を読み取る" + "get_attachment_content": "添付ファイルの内容を読み取る", + "rename_note": "ノート名を変更", + "delete_note": "ノートを削除", + "move_note": "ノートを移動", + "clone_note": "ノートをクローン" } }, "ocr": { From 0150b5b61e39de17fc05ac8f2f4acc281d6635e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Sun, 12 Apr 2026 14:51:00 +0200 Subject: [PATCH 035/203] Translated using Weblate (Irish) Currently translated at 99.9% (1863 of 1864 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/ --- .../src/translations/ga/translation.json | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/apps/client/src/translations/ga/translation.json b/apps/client/src/translations/ga/translation.json index 6854c3ff77..69819a83b2 100644 --- a/apps/client/src/translations/ga/translation.json +++ b/apps/client/src/translations/ga/translation.json @@ -121,12 +121,24 @@ "delete_notes": { "close": "Dún", "delete_all_clones_description": "Scrios gach clón freisin (is féidir é seo a chealú in athruithe le déanaí)", - "erase_notes_description": "Ní mharcálann scriosadh gnáth (bog) ach na nótaí mar scriosta agus is féidir iad a dhíscriosadh (sa dialóg athruithe le déanaí) laistigh de thréimhse ama. Scriosfar na nótaí láithreach má sheiceálann tú an rogha seo agus ní bheidh sé indéanta na nótaí a dhíscriosadh.", + "erase_notes_description": "Scrios nótaí láithreach in ionad scriosadh bog. Ní féidir é seo a chealú agus cuirfidh sé iallach ort an feidhmchlár a athlódáil.", "erase_notes_warning": "Scrios nótaí go buan (ní féidir é seo a chealú), lena n-áirítear na clónanna go léir. Cuirfidh sé seo iallach ar an bhfeidhmchlár athlódáil.", - "notes_to_be_deleted": "Scriosfar na nótaí seo a leanas ({{notesCount}})", + "notes_to_be_deleted": "Nótaí le scriosadh ({{notesCount}})", "no_note_to_delete": "Ní scriosfar aon nóta (clóin amháin).", - "broken_relations_to_be_deleted": "Brisfear agus scriosfar na caidrimh seo a leanas ({{ relationCount}})", - "cancel": "Cealaigh" + "broken_relations_to_be_deleted": "Caidrimh bhriste ({{relationCount}})", + "cancel": "Cealaigh", + "title": "Scrios nótaí", + "clones_label": "Clóin", + "delete_clones_description_one": "Scrios {{count}} clón eile freisin. Is féidir é seo a chealú sna hathruithe is déanaí.", + "delete_clones_description_two": "Scrios {{count}} clóin eile freisin. Is féidir é seo a chealú sna hathruithe is déanaí.", + "delete_clones_description_few": "Scrios {{count}} clóin eile freisin. Is féidir é seo a chealú sna hathruithe is déanaí.", + "delete_clones_description_many": "Scrios {{count}} clóin eile freisin. Is féidir é seo a chealú sna hathruithe is déanaí.", + "delete_clones_description_other": "Scrios {{count}} clóin eile freisin. Is féidir é seo a chealú sna hathruithe is déanaí.", + "erase_notes_label": "Scrios go buan", + "table_note_with_relation": "Nóta le gaol", + "table_relation": "Gaol", + "table_points_to": "Pointí chuig (scriosta)", + "delete": "Scrios" }, "export": { "export_note_title": "Nóta easpórtála", @@ -237,7 +249,8 @@ "box_size_small": "beag (~ 10 líne)", "box_size_medium": "meánach (~ 30 líne)", "box_size_full": "lán (taispeánann an bosca an téacs iomlán)", - "button_include": "Cuir nóta san áireamh" + "button_include": "Cuir nóta san áireamh", + "box_size_expandable": "inleathnaithe (fillte de réir réamhshocraithe)" }, "info": { "modalTitle": "Teachtaireacht eolais", @@ -809,7 +822,10 @@ "board": "Bord", "presentation": "Cur i Láthair", "include_archived_notes": "Taispeáin nótaí cartlannaithe", - "hide_child_notes": "Folaigh nótaí leanaí sa chrann" + "hide_child_notes": "Folaigh nótaí leanaí sa chrann", + "open_all_in_tabs": "Oscail gach rud", + "open_all_in_tabs_tooltip": "Oscail na torthaí go léir i gcluaisíní nua", + "open_all_confirm": "Osclóidh sé seo {{count}} nótaí i gcluaisíní nua. Lean ar aghaidh?" }, "edited_notes": { "no_edited_notes_found": "Gan aon nótaí eagarthóireachta ar an lá seo go fóill...", @@ -863,7 +879,8 @@ "collapse": "Laghdaigh go dtí an gnáthmhéid", "title": "Léarscáil Nótaí", "fix-nodes": "Deisigh nóid", - "link-distance": "Fad naisc" + "link-distance": "Fad naisc", + "too-many-notes": "Tá {{count}} nótaí sa fho-chrann seo, rud a sháraíonn an teorainn {{max}} is féidir a thaispeáint sa léarscáil nótaí." }, "note_paths": { "title": "Cosáin Nótaí", @@ -1368,7 +1385,8 @@ "date-and-time": "Dáta & am", "path": "Cosán", "database_backed_up_to": "Tá cúltaca déanta den bhunachar sonraí chuig {{backupFilePath}}", - "no_backup_yet": "gan aon chúltaca fós" + "no_backup_yet": "gan aon chúltaca fós", + "download": "Íoslódáil" }, "etapi": { "title": "ETAPI", @@ -1489,7 +1507,8 @@ "test_title": "Tástáil Sioncrónaithe", "test_description": "Déanfaidh sé seo tástáil ar an nasc agus ar an gcroitheadh láimhe leis an bhfreastalaí sioncrónaithe. Mura bhfuil an freastalaí sioncrónaithe tosaithe, socróidh sé seo é chun sioncrónú leis an doiciméad áitiúil.", "test_button": "Tástáil sioncrónaithe", - "handshake_failed": "Theip ar chroitheadh láimhe an fhreastalaí sioncrónaithe, earráid: {{message}}" + "handshake_failed": "Theip ar chroitheadh láimhe an fhreastalaí sioncrónaithe, earráid: {{message}}", + "timeout_description": "Cé chomh fada is ceart fanacht sula dtugann tú suas ar nasc sioncrónaithe mall. Méadaigh an méid ama má tá líonra éagobhsaí agat." }, "api_log": { "close": "Dún" @@ -1803,7 +1822,8 @@ "theme_none": "Gan aon aibhsiú comhréire", "theme_group_light": "Téamaí éadroma", "theme_group_dark": "Téamaí dorcha", - "copy_title": "Cóipeáil chuig an ghearrthaisce" + "copy_title": "Cóipeáil chuig an ghearrthaisce", + "click_to_copy": "Cliceáil chun cóipeáil" }, "classic_editor_toolbar": { "title": "Formáidiú" @@ -2328,7 +2348,8 @@ "note_context_disabled": "Cliceáil chun an nóta reatha a chur san áireamh i gcomhthéacs", "no_provider_message": "Níl aon soláthraí AI cumraithe. Cuir ceann leis chun comhrá a thosú.", "add_provider": "Cuir Soláthraí AI leis", - "sources_summary": "{{count}} foinsí ó {{sites}} suíomhanna" + "sources_summary": "{{count}} foinsí ó {{sites}} suíomhanna", + "stop": "Stop" }, "sidebar_chat": { "title": "Comhrá AI", @@ -2392,7 +2413,11 @@ "web_search": "Cuardach gréasáin", "note_in_parent": " i ", "get_attachment": "Faigh ceangaltán", - "get_attachment_content": "Léigh ábhar an cheangail" + "get_attachment_content": "Léigh ábhar an cheangail", + "rename_note": "Athainmnigh an nóta", + "delete_note": "Scrios nóta", + "move_note": "Bog nóta", + "clone_note": "Nóta clón" } }, "ocr": { From 7ec5945517a02d1bfdc31eb62db25c9ea9b5cb58 Mon Sep 17 00:00:00 2001 From: green Date: Sun, 12 Apr 2026 04:46:30 +0200 Subject: [PATCH 036/203] Translated using Weblate (Japanese) Currently translated at 100.0% (402 of 402 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ja/ --- apps/server/src/assets/translations/ja/server.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/server/src/assets/translations/ja/server.json b/apps/server/src/assets/translations/ja/server.json index fb6fbf5686..c1f65d4cd4 100644 --- a/apps/server/src/assets/translations/ja/server.json +++ b/apps/server/src/assets/translations/ja/server.json @@ -455,5 +455,8 @@ "fulltext-after-expression": "\"{{- token}}\" は有効な式ではありません。テキストを検索するには、属性フィルターの前に記述してください(例: \"#label {{- token}}\" ではなく \"{{- token}} #label\")。", "unrecognized-expression": "認識されない式 \"{{- token}}\"" } + }, + "script": { + "wrong-environment": "ノート \"{{- noteTitle}}\" ({{- noteId}}) を実行できません。これは {{- actualEnv}} スクリプトですが、{{- expectedEnv}} で実行しようとしました。" } } From 37df2dd9b2113d3faebf3c539a70c0bcdcd51c8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 06:57:10 +0000 Subject: [PATCH 037/203] chore(deps): update dependency vite to v8.0.8 --- apps/server/package.json | 2 +- apps/website/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 384 +++++++++++++++++++++++++++++--------- 4 files changed, 301 insertions(+), 89 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index ab31391524..d117a24a74 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -130,7 +130,7 @@ "tmp": "0.2.5", "turnish": "1.8.0", "unescape": "1.0.1", - "vite": "8.0.7", + "vite": "8.0.8", "ws": "8.20.0", "xml2js": "0.6.2", "yauzl": "3.3.0" diff --git a/apps/website/package.json b/apps/website/package.json index 4e546b4ee4..e0f95b7596 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -21,7 +21,7 @@ "eslint-config-preact": "2.0.0", "typescript": "6.0.2", "user-agent-data-types": "0.4.3", - "vite": "8.0.7", + "vite": "8.0.8", "vitest": "4.1.4" }, "eslintConfig": { diff --git a/package.json b/package.json index 6e8cc64a5f..9044cd6aba 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "tsx": "4.21.0", "typescript": "6.0.2", "typescript-eslint": "8.58.1", - "vite": "8.0.7", + "vite": "8.0.8", "vite-plugin-dts": "4.5.4", "vitest": "4.1.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ffe1ba222..0178434b7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,7 +107,7 @@ importers: version: 24.12.2 '@vitest/browser-webdriverio': specifier: 4.1.4 - version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/coverage-v8': specifier: 4.1.4 version: 4.1.4(@vitest/browser@4.1.4)(vitest@4.1.4) @@ -161,7 +161,7 @@ importers: version: 0.18.0 rollup-plugin-webpack-stats: specifier: 3.1.0 - version: 3.1.0(rolldown@1.0.0-rc.13)(rollup@4.60.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.1.0(rolldown@1.0.0-rc.15)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) tslib: specifier: 2.8.1 version: 2.8.1 @@ -175,14 +175,14 @@ importers: specifier: 8.58.1 version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) vite: - specifier: 8.0.7 - version: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + specifier: 8.0.8 + version: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-dts: specifier: 4.5.4 - version: 4.5.4(@types/node@24.12.2)(rollup@4.60.1)(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.60.1)(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) apps/build-docs: devDependencies: @@ -384,7 +384,7 @@ importers: version: 5.0.0 '@prefresh/vite': specifier: 3.0.0 - version: 3.0.0(preact@10.29.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.0.0(preact@10.29.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) '@types/bootstrap': specifier: 5.2.10 version: 5.2.10 @@ -417,7 +417,7 @@ importers: version: 0.7.2 vite-plugin-static-copy: specifier: 4.0.1 - version: 4.0.1(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.0.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) apps/db-compare: dependencies: @@ -864,8 +864,8 @@ importers: specifier: 1.0.1 version: 1.0.1 vite: - specifier: 8.0.7 - version: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + specifier: 8.0.8 + version: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) ws: specifier: 8.20.0 version: 8.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -915,7 +915,7 @@ importers: devDependencies: '@preact/preset-vite': specifier: 2.10.5 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) eslint: specifier: 10.2.0 version: 10.2.0(jiti@2.6.1) @@ -929,11 +929,11 @@ importers: specifier: 0.4.3 version: 0.4.3 vite: - specifier: 8.0.7 - version: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + specifier: 8.0.8 + version: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) packages/ckeditor5: dependencies: @@ -975,7 +975,7 @@ importers: version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': specifier: 4.1.4 - version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': specifier: 4.1.4 version: 4.1.4(vitest@4.1.4) @@ -999,10 +999,10 @@ importers: version: 6.0.2 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.0.0(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1020,7 +1020,7 @@ importers: version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': specifier: 4.1.4 - version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': specifier: 4.1.4 version: 4.1.4(vitest@4.1.4) @@ -1044,10 +1044,10 @@ importers: version: 6.0.2 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.0.0(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1065,7 +1065,7 @@ importers: version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': specifier: 4.1.4 - version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': specifier: 4.1.4 version: 4.1.4(vitest@4.1.4) @@ -1089,10 +1089,10 @@ importers: version: 6.0.2 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.0.0(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1117,7 +1117,7 @@ importers: version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': specifier: 4.1.4 - version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': specifier: 4.1.4 version: 4.1.4(vitest@4.1.4) @@ -1141,10 +1141,10 @@ importers: version: 6.0.2 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.0.0(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1162,7 +1162,7 @@ importers: version: 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) '@vitest/browser': specifier: 4.1.4 - version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + version: 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/coverage-istanbul': specifier: 4.1.4 version: 4.1.4(vitest@4.1.4) @@ -1186,10 +1186,10 @@ importers: version: 6.0.2 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 2.0.0(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: specifier: 9.27.0 version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1451,7 +1451,7 @@ importers: version: 20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5) vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) packages/turndown-plugin-gfm: devDependencies: @@ -1463,7 +1463,7 @@ importers: version: 7.2.4 vitest: specifier: 4.1.4 - version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) packages: @@ -2312,15 +2312,24 @@ packages: '@emnapi/core@1.9.1': resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.9.0': resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==} '@emnapi/runtime@1.9.1': resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/wasi-threads@1.2.0': resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emotion/is-prop-valid@1.4.0': resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} @@ -3964,6 +3973,9 @@ packages: '@oxc-project/types@0.123.0': resolution: {integrity: sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==} + '@oxc-project/types@0.124.0': + resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@panva/asn1.js@1.0.0': resolution: {integrity: sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==} engines: {node: '>=10.13.0'} @@ -4853,30 +4865,60 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-rc.13': resolution: {integrity: sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.13': resolution: {integrity: sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-rc.13': resolution: {integrity: sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.13': resolution: {integrity: sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.13': resolution: {integrity: sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4884,6 +4926,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.13': resolution: {integrity: sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4891,6 +4940,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.13': resolution: {integrity: sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4898,6 +4954,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.13': resolution: {integrity: sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4905,6 +4968,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.13': resolution: {integrity: sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4912,6 +4982,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.13': resolution: {integrity: sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4919,32 +4996,65 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.13': resolution: {integrity: sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.13': resolution: {integrity: sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==} engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.13': resolution: {integrity: sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.13': resolution: {integrity: sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-rc.13': resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==} + '@rolldown/pluginutils@1.0.0-rc.15': + resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + '@rollup/plugin-buble@1.0.3': resolution: {integrity: sha512-QYD9BKkJoof0FdCFeSYYhF6/Y8e0Mnf+098xGgmWOFJ4UPHlWujjqOYeVwEm2hJPOmlR5k7HPUdAjqtOWhN64Q==} engines: {node: '>=14.0.0'} @@ -11992,6 +12102,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.0-rc.15: + resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-stats@2.1.0: resolution: {integrity: sha512-mkthlSVb8T2AKfdt/FEJqB/1Nvsr4G+otKZ4dhPKjyWQA6fdL7STRPA4epwVOBsj1yYYv/dcgAmfwfNtMpZ6sw==} engines: {node: '>=18'} @@ -13492,8 +13607,8 @@ packages: peerDependencies: vite: '>=7.3.2' - vite@8.0.7: - resolution: {integrity: sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==} + vite@8.0.8: + resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -15902,6 +16017,12 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.9.0': dependencies: tslib: 2.8.1 @@ -15912,11 +16033,21 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/is-prop-valid@1.4.0': dependencies: '@emotion/memoize': 0.9.0 @@ -17345,6 +17476,13 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -17490,6 +17628,8 @@ snapshots: '@oxc-project/types@0.123.0': {} + '@oxc-project/types@0.124.0': {} + '@panva/asn1.js@1.0.0': {} '@paralleldrive/cuid2@2.2.2': @@ -17579,19 +17719,19 @@ snapshots: '@popperjs/core@2.11.8': {} - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.1)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@prefresh/vite': 2.4.12(preact@10.29.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.1.4(rollup@4.60.1) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3 magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) - vite-prerender-plugin: 0.5.11(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite-prerender-plugin: 0.5.11(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -17611,19 +17751,19 @@ snapshots: dependencies: preact: 10.29.1 - '@prefresh/rolldown@0.1.0(rolldown@1.0.0-rc.13)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': + '@prefresh/rolldown@0.1.0(rolldown@1.0.0-rc.13)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: oxc-unshadowed-visitor: 0.0.1(rolldown@1.0.0-rc.13) rolldown: 1.0.0-rc.13 rolldown-string: 0.3.0(rolldown@1.0.0-rc.13) optionalDependencies: - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - oxc-parser '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': + '@prefresh/vite@2.4.12(preact@10.29.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@prefresh/babel-plugin': 0.5.2 @@ -17631,21 +17771,21 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.29.1 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color - '@prefresh/vite@3.0.0(preact@10.29.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': + '@prefresh/vite@3.0.0(preact@10.29.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@prefresh/babel-plugin': 0.5.2 '@prefresh/core': 1.5.5(preact@10.29.1) - '@prefresh/rolldown': 0.1.0(rolldown@1.0.0-rc.13)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@prefresh/rolldown': 0.1.0(rolldown@1.0.0-rc.13)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.29.1 rolldown: 1.0.0-rc.13 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - oxc-parser - supports-color @@ -18423,39 +18563,75 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-rc.13': optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.15': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.13': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.13': optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.13': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.13': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.13': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.13': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.13': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.13': optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.13': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.13': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.13': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.13': dependencies: '@emnapi/core': 1.9.1 @@ -18463,14 +18639,29 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.13': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.13': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + optional: true + '@rolldown/pluginutils@1.0.0-rc.13': {} + '@rolldown/pluginutils@1.0.0-rc.15': {} + '@rollup/plugin-buble@1.0.3(rollup@4.60.1)': dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.60.1) @@ -20697,10 +20888,10 @@ snapshots: '@vercel/oidc@3.1.0': {} - '@vitest/browser-webdriverio@4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': + '@vitest/browser-webdriverio@4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: - '@vitest/browser': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) - vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/browser': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) webdriverio: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -20708,16 +20899,16 @@ snapshots: - utf-8-validate - vite - '@vitest/browser@4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)': + '@vitest/browser@4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils': 4.1.4 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -20737,7 +20928,7 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 tinyrainbow: 3.1.0 - vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - supports-color @@ -20753,9 +20944,9 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: - '@vitest/browser': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) + '@vitest/browser': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4) '@vitest/expect@4.1.4': dependencies: @@ -20766,14 +20957,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.7.5(@types/node@24.12.2)(typescript@6.0.2) - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@4.1.4': dependencies: @@ -20802,7 +20993,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils@4.1.4': dependencies: @@ -27666,19 +27857,40 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.13 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.13 - rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.13)(rollup@4.60.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): - optionalDependencies: - rolldown: 1.0.0-rc.13 - rollup: 4.60.1 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) - - rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.13)(rollup@4.60.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + rolldown@1.0.0-rc.15: dependencies: - rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.13)(rollup@4.60.1)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@oxc-project/types': 0.124.0 + '@rolldown/pluginutils': 1.0.0-rc.15 optionalDependencies: - rolldown: 1.0.0-rc.13 + '@rolldown/binding-android-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-x64': 1.0.0-rc.15 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 + + rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.15)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + optionalDependencies: + rolldown: 1.0.0-rc.15 rollup: 4.60.1 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + + rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.15)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.15)(rollup@4.60.1)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + optionalDependencies: + rolldown: 1.0.0-rc.15 + rollup: 4.60.1 + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) rollup@4.60.1: dependencies: @@ -29323,7 +29535,7 @@ snapshots: es-module-lexer: 2.0.0 obug: 2.1.1 pathe: 2.0.3 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -29338,7 +29550,7 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.5.4(@types/node@24.12.2)(rollup@4.60.1)(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-dts@4.5.4(@types/node@24.12.2)(rollup@4.60.1)(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@microsoft/api-extractor': 7.52.8(@types/node@24.12.2) '@rollup/pluginutils': 5.1.4(rollup@4.60.1) @@ -29351,27 +29563,27 @@ snapshots: magic-string: 0.30.21 typescript: 6.0.2 optionalDependencies: - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-static-copy@4.0.1(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-static-copy@4.0.1(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: chokidar: 3.6.0 p-map: 7.0.4 picocolors: 1.1.1 tinyglobby: 0.2.15 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-svgo@2.0.0(typescript@6.0.2)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-svgo@2.0.0(typescript@6.0.2)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: svgo: 4.0.1 typescript: 6.0.2 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) - vite-prerender-plugin@0.5.11(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + vite-prerender-plugin@0.5.11(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -29379,14 +29591,14 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) - vite@8.0.7(@types/node@24.12.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.8(@types/node@24.12.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.8 - rolldown: 1.0.0-rc.13 + postcss: 8.5.9 + rolldown: 1.0.0-rc.15 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.12.2 @@ -29400,12 +29612,12 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.8 - rolldown: 1.0.0-rc.13 + postcss: 8.5.9 + rolldown: 1.0.0-rc.15 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.12.2 @@ -29419,10 +29631,10 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@24.12.2)(@vitest/browser-webdriverio@4.1.4)(@vitest/coverage-istanbul@4.1.4)(@vitest/coverage-v8@4.1.4)(@vitest/ui@4.1.4)(happy-dom@20.8.9(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.4(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 @@ -29439,12 +29651,12 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 24.12.2 - '@vitest/browser-webdriverio': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.7(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + '@vitest/browser-webdriverio': 4.1.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.2)(typescript@6.0.2))(utf-8-validate@6.0.5)(vite@8.0.8(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.4)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/coverage-istanbul': 4.1.4(vitest@4.1.4) '@vitest/coverage-v8': 4.1.4(@vitest/browser@4.1.4)(vitest@4.1.4) '@vitest/ui': 4.1.4(vitest@4.1.4) @@ -29841,7 +30053,7 @@ snapshots: scule: 1.3.0 tinyglobby: 0.2.15 unimport: 5.6.0 - vite: 8.0.7(@types/node@24.12.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.8(@types/node@24.12.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) vite-node: 5.3.0(@types/node@24.12.2)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.3) web-ext-run: 0.2.4 optionalDependencies: From ef9002dedea9101697869bf52e155a2b36087261 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 09:58:13 +0300 Subject: [PATCH 038/203] Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- apps/client/src/translations/ga/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/translations/ga/translation.json b/apps/client/src/translations/ga/translation.json index 69819a83b2..ee66bc7593 100644 --- a/apps/client/src/translations/ga/translation.json +++ b/apps/client/src/translations/ga/translation.json @@ -2417,7 +2417,7 @@ "rename_note": "Athainmnigh an nóta", "delete_note": "Scrios nóta", "move_note": "Bog nóta", - "clone_note": "Nóta clón" + "clone_note": "Clónáil nóta" } }, "ocr": { From 9b2be5736586b6b0122e01d11334266ca00632ed Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 12:56:50 +0300 Subject: [PATCH 039/203] docs: remove search analysis --- docs/search-performance-benchmarks.md | 111 -------------------------- 1 file changed, 111 deletions(-) delete mode 100644 docs/search-performance-benchmarks.md diff --git a/docs/search-performance-benchmarks.md b/docs/search-performance-benchmarks.md deleted file mode 100644 index 17b9427ba5..0000000000 --- a/docs/search-performance-benchmarks.md +++ /dev/null @@ -1,111 +0,0 @@ -# Search Performance Benchmarks - -Comparison of `main` vs `feat/search-perf-take1` branch. - -> **Methodology:** In-memory benchmarks using synthetic datasets with monkeypatched `getContent()`. Both branches tested on the same machine in the same session. Times are avg of 5 iterations with warm caches. Note content I/O (`NoteContentFulltextExp` blob scan) is not measured — these numbers reflect the in-memory pipeline only. -> -> **Benchmark source:** `apps/server/src/services/search/services/search_benchmark.spec.ts` - ---- - -## End-to-End Results at 10K Notes - -### Autocomplete (typing in the search bar, `fastSearch=true`) - -| Query | main | this PR | Change | -|:------|-----:|--------:|-------:| -| `"meeting"` | 24.7ms | 14.3ms | **-42%** | -| `"meeting notes"` | 33.0ms | 15.6ms | **-53%** | -| `"meeting notes january"` | 43.2ms | 17.7ms | **-59%** | -| `"documentation"` | 17.5ms | 11.0ms | **-37%** | -| `"note"` (matches 85% of notes) | 90.8ms | 46.4ms | **-49%** | -| `"projct"` (typo, fuzzy ON) | 100.7ms | 6.0ms | **-94%** | -| `"xyznonexistent"` (no match, fuzzy ON) | 18.2ms | 6.0ms | **-67%** | -| `"xyzfoo xyzbar"` (no match, fuzzy ON) | 63.4ms | 7.1ms | **-89%** | - -### Full Search (pressing Enter, `fastSearch=false`) - -| Query | main | this PR | Change | -|:------|-----:|--------:|-------:| -| `"meeting"` | 22.9ms | 19.6ms | **-14%** | -| `"meeting notes"` | 35.7ms | 17.4ms | **-51%** | -| `"meeting notes january"` | 43.4ms | 21.0ms | **-52%** | -| `"quarterly budget review report"` | 37.1ms | 18.3ms | **-51%** | -| `"project planning"` | 27.4ms | 17.3ms | **-37%** | - -### Full Search with Fuzzy Matching - -| Query | main | this PR | Change | -|:------|-----:|--------:|-------:| -| `"meeting"` | 23.3ms | 17.8ms | **-24%** | -| `"meeting notes"` | 33.8ms | 18.6ms | **-45%** | -| `"meeting notes january"` | 43.2ms | 18.0ms | **-58%** | -| `"quarterly budget review report"` | 39.5ms | 17.2ms | **-56%** | -| `"project planning"` | 32.8ms | 18.6ms | **-43%** | -| `"projct planing"` (typo, recovers 1,500 results) | 133.8ms | 94.8ms | **-29%** | -| `"xyzfoo xyzbar"` (no match, worst case) | 64.2ms | 61.4ms | -4% | - ---- - -## Scaling Behavior - -### Autocomplete: `"meeting notes"` (fuzzy OFF) - -| Notes | main | this PR | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 2.7ms | 1.1ms | **-59%** | -| 5,000 | 15.8ms | 5.9ms | **-63%** | -| 10,000 | 33.0ms | 15.6ms | **-53%** | -| 20,000 | 67.3ms | 33.6ms | **-50%** | - -### Full search: `"meeting notes january"` (fuzzy ON) - -| Notes | main | this PR | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 3.7ms | 1.3ms | **-65%** | -| 5,000 | 21.2ms | 8.7ms | **-59%** | -| 10,000 | 43.2ms | 18.0ms | **-58%** | -| 20,000 | 92.8ms | 40.1ms | **-57%** | - -### Autocomplete no-match: `"xyzfoo xyzbar"` (fuzzy ON) - -| Notes | main | this PR | Change | -|------:|-----:|--------:|-------:| -| 1,000 | 5.1ms | 0.4ms | **-92%** | -| 5,000 | 29.0ms | 2.2ms | **-92%** | -| 10,000 | 63.4ms | 7.1ms | **-89%** | -| 20,000 | 128.8ms | 19.1ms | **-85%** | - -### Typing progression at 10K notes (autocomplete, fuzzy OFF) - -| Prefix typed | main | this PR | Change | -|:-------------|-----:|--------:|-------:| -| `"d"` | 66.9ms | 44.8ms | **-33%** | -| `"doc"` | 20.9ms | 14.7ms | **-30%** | -| `"document"` | 16.8ms | 11.8ms | **-30%** | -| `"documentation"` | 17.5ms | 11.0ms | **-37%** | - ---- - -## What Changed - -1. **Pre-built flat text index** with incremental dirty-marking in Becca — avoids rebuilding per-note flat text on every search -2. **Skip two-phase fuzzy fallback for autocomplete** — the user is still typing, fuzzy adds latency for no benefit -3. **Pre-normalized attribute names/values** cached on `BAttribute` at construction time -4. **Cached normalized parent titles** per search execution via `Map` in `NoteFlatTextExp` -5. **Set-based token lookup** in `searchPathTowardsRoot` (O(1) vs O(n) `Array.includes`) -6. **Removed redundant `toLowerCase()`** — `normalizeSearchText` already lowercases; callers were double-lowering -7. **Pre-normalize tokens once** in `addScoreForStrings` instead of re-normalizing per chunk -8. **Skip edit distance computation** when fuzzy matching is disabled -9. **Faster content snippet extraction** — regex `/<[^>]*>/g` instead of `striptags` library; normalize only the snippet window, not full content -10. **`removeDiacritic()` hoisted outside regex while-loop** in highlighting -11. **Single-token autocomplete fast path** — skips the recursive parent walk entirely, uses `getBestNotePath()` directly -12. **User option `searchEnableFuzzyMatching`** — lets users disable fuzzy matching for fastest possible search - ---- - -## Known Limitations - -- These benchmarks measure the **in-memory pipeline only** (titles, attributes, scoring, highlighting). The `NoteContentFulltextExp` sequential blob scan from SQLite is not exercised because `getContent()` is monkeypatched. In production, the full search path (`fastSearch=false`) includes reading every note's content from disk, which adds significant time at scale. -- Fuzzy matching on the full-search two-phase path shows slight regressions (+9-12%) for single-token queries because edit distance computation cost hasn't changed on that path. Multi-token queries still improve because the token normalization and tree walk optimizations apply to both paths. -- At 1K notes, some results show noise-level regressions. The optimizations target 5K+ note scales where overhead is measurable. From 6e90a4168e56e50fc06821eb11c1f694a9d42b06 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 13:02:28 +0300 Subject: [PATCH 040/203] feat(autocomplete): toggle for fuzzy matching (closes #8360) --- CLAUDE.md | 5 +++-- apps/client/src/translations/en/translation.json | 3 ++- apps/client/src/widgets/type_widgets/options/other.tsx | 7 +++++++ apps/server/src/routes/api/options.ts | 1 + apps/server/src/services/options_init.ts | 1 + apps/server/src/services/search/services/search.ts | 9 +++++---- packages/commons/src/lib/options_interface.ts | 2 ++ 7 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1b90b02881..a395f985bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,8 +162,9 @@ Trilium provides powerful user scripting capabilities: - To add a new user preference: 1. Add the option type to `OptionDefinitions` in `packages/commons/src/lib/options_interface.ts` 2. Add a default value in `apps/server/src/services/options_init.ts` in the `defaultOptions` array - 3. **Whitelist the option** in `apps/server/src/routes/api/options.ts` by adding it to `ALLOWED_OPTIONS` (required for client updates) - 4. Use `useTriliumOption("optionName")` hook in React components to read/write the option + 3. **Whitelist the option** in `apps/server/src/routes/api/options.ts` by adding it to the `ALLOWED_OPTIONS` array — **without this, the API will reject changes with "Option 'X' is not allowed to be changed"** + 4. If the option should be user-editable in the UI, add a control in the appropriate settings component (e.g., `apps/client/src/widgets/type_widgets/options/other.tsx`) and a translation key in `apps/client/src/translations/en/translation.json` + 5. Use `useTriliumOption("optionName")` hook in React components to read/write the option - Available hooks: `useTriliumOption` (string), `useTriliumOptionBool`, `useTriliumOptionInt`, `useTriliumOptionJson` - See `docs/Developer Guide/Developer Guide/Concepts/Options/Creating a new option.md` for detailed documentation diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 2c1e1ae19a..abc3de3de4 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1326,7 +1326,8 @@ }, "search": { "title": "Search", - "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)" + "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)", + "enable_autocomplete_fuzzy": "Enable fuzzy matching for autocomplete (slower, but tolerates typos while typing)" }, "search_engine": { "title": "Search Engine", diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index 8cb99bace4..b75c81b711 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -39,6 +39,7 @@ export default function OtherSettings() { function SearchSettings() { const [ fuzzyEnabled, setFuzzyEnabled ] = useTriliumOptionBool("searchEnableFuzzyMatching"); + const [ autocompleteFuzzy, setAutocompleteFuzzy ] = useTriliumOptionBool("searchAutocompleteFuzzy"); return ( @@ -48,6 +49,12 @@ function SearchSettings() { currentValue={fuzzyEnabled} onChange={setFuzzyEnabled} /> + ); } diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 384c975eba..9be9ba0670 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -100,6 +100,7 @@ const ALLOWED_OPTIONS = new Set([ "backgroundEffects", "allowedHtmlTags", "searchEnableFuzzyMatching", + "searchAutocompleteFuzzy", "redirectBareDomain", "showLoginInShareTheme", "splitEditorOrientation", diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index 9ac33d4e31..4bff15d91e 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -236,6 +236,7 @@ const defaultOptions: DefaultOption[] = [ // Search settings { name: "searchEnableFuzzyMatching", value: "true", isSynced: true }, + { name: "searchAutocompleteFuzzy", value: "false", isSynced: true }, // Share settings { name: "redirectBareDomain", value: "false", isSynced: true }, diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index 226c809d7d..660c46119b 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -17,6 +17,7 @@ import type Expression from "../expressions/expression.js"; import sql from "../../sql.js"; import scriptService from "../../script.js"; import protectedSessionService from "../../protected_session.js"; +import optionService from "../../options.js"; export interface SearchNoteResult { searchResultNoteIds: string[]; @@ -248,11 +249,11 @@ function findResultsWithExpression(expression: Expression, searchContext: Search return performSearch(expression, searchContext, false); } - // For autocomplete searches, skip the expensive two-phase fuzzy fallback. - // The user is typing and will refine their query — exact matching is - // sufficient and avoids a second full scan of all notes. + // For autocomplete searches, use the dedicated autocomplete fuzzy option. + // Default is off for faster response; users can enable if they want typo tolerance. if (searchContext.autocomplete) { - return performSearch(expression, searchContext, false); + const autocompleteFuzzy = optionService.getOptionBool("searchAutocompleteFuzzy"); + return performSearch(expression, searchContext, autocompleteFuzzy); } // Phase 1: Try exact matches first (without fuzzy matching) diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index d57217712e..a00666677a 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -138,6 +138,8 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Mon, 13 Apr 2026 13:05:54 +0300 Subject: [PATCH 041/203] refactor(search): simplify branching for autocomplete --- apps/server/src/services/search/services/search.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index 660c46119b..f9581aa170 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -236,6 +236,12 @@ function findResultsWithExpression(expression: Expression, searchContext: Search loadNeededInfoFromDatabase(); } + // For autocomplete searches, use the dedicated autocomplete fuzzy option + // instead of the global fuzzy setting. + if (searchContext.autocomplete) { + searchContext.enableFuzzyMatching = optionService.getOptionBool("searchAutocompleteFuzzy"); + } + // If there's an explicit orderBy clause, skip progressive search // as it would interfere with the ordering if (searchContext.orderBy) { @@ -249,13 +255,6 @@ function findResultsWithExpression(expression: Expression, searchContext: Search return performSearch(expression, searchContext, false); } - // For autocomplete searches, use the dedicated autocomplete fuzzy option. - // Default is off for faster response; users can enable if they want typo tolerance. - if (searchContext.autocomplete) { - const autocompleteFuzzy = optionService.getOptionBool("searchAutocompleteFuzzy"); - return performSearch(expression, searchContext, autocompleteFuzzy); - } - // Phase 1: Try exact matches first (without fuzzy matching) const exactResults = performSearch(expression, searchContext, false); From 597c6eb15b86346bb620f6f7e9946e743a81a957 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 13:13:29 +0300 Subject: [PATCH 042/203] chore(options): improve descriptions for search --- .../src/translations/en/translation.json | 6 ++-- .../widgets/type_widgets/options/other.tsx | 33 +++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index abc3de3de4..25e6940e9a 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1326,8 +1326,10 @@ }, "search": { "title": "Search", - "enable_fuzzy_matching": "Enable fuzzy matching in search (matches similar words when exact matches are insufficient)", - "enable_autocomplete_fuzzy": "Enable fuzzy matching for autocomplete (slower, but tolerates typos while typing)" + "fuzzy_matching_label": "Typo tolerance in search", + "fuzzy_matching_description": "Affects quick search and full search. Finds similar words when exact matches are insufficient.", + "autocomplete_fuzzy_label": "Typo tolerance in autocomplete", + "autocomplete_fuzzy_description": "Affects jump-to-note and note selectors. Slower but tolerates typos." }, "search_engine": { "title": "Search Engine", diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index b75c81b711..b1ccfcfcc0 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -14,7 +14,9 @@ import FormGroup from "../../react/FormGroup"; import FormSelect from "../../react/FormSelect"; import FormText from "../../react/FormText"; import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox"; +import FormToggle from "../../react/FormToggle"; import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks"; +import OptionsRow from "./components/OptionsRow"; import OptionsSection from "./components/OptionsSection"; import TimeSelector from "./components/TimeSelector"; @@ -43,18 +45,29 @@ function SearchSettings() { return ( - - + + + + + label={t("search.autocomplete_fuzzy_label")} + description={t("search.autocomplete_fuzzy_description")} + > + + ); } From ead70ad39411248fc020c24d0d46f0308745a3ba Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 13:20:42 +0300 Subject: [PATCH 043/203] fix(autocomplete): fuzzy search not working if the search one was not enabled --- apps/server/src/services/search/services/search.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index f9581aa170..ea1d20c263 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -236,12 +236,6 @@ function findResultsWithExpression(expression: Expression, searchContext: Search loadNeededInfoFromDatabase(); } - // For autocomplete searches, use the dedicated autocomplete fuzzy option - // instead of the global fuzzy setting. - if (searchContext.autocomplete) { - searchContext.enableFuzzyMatching = optionService.getOptionBool("searchAutocompleteFuzzy"); - } - // If there's an explicit orderBy clause, skip progressive search // as it would interfere with the ordering if (searchContext.orderBy) { @@ -415,6 +409,12 @@ function findResultsWithQuery(query: string, searchContext: SearchContext): Sear query = query || ""; searchContext.originalQuery = query; + // For autocomplete searches, use the dedicated autocomplete fuzzy option + // instead of the global fuzzy setting. Do this early so it applies to all code paths. + if (searchContext.autocomplete) { + searchContext.enableFuzzyMatching = optionService.getOptionBool("searchAutocompleteFuzzy"); + } + const expression = parseQueryToExpression(query, searchContext); if (!expression) { From 6763f4f40396f871f3c614f7d12aa780d516599b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 13:29:58 +0300 Subject: [PATCH 044/203] chore(becca): add log for cache memory consumption --- apps/server/src/becca/becca-interface.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/server/src/becca/becca-interface.ts b/apps/server/src/becca/becca-interface.ts index 6d5d13d07b..f1e7c7fde3 100644 --- a/apps/server/src/becca/becca-interface.ts +++ b/apps/server/src/becca/becca-interface.ts @@ -1,4 +1,6 @@ import sql from "../services/sql.js"; +import log from "../services/log.js"; +import { formatSize } from "../services/utils.js"; import NoteSet from "../services/search/note_set.js"; import NotFoundError from "../errors/not_found_error.js"; import type BOption from "./entities/boption.js"; @@ -277,6 +279,9 @@ export default class Becca { */ getFlatTextIndex(): { notes: BNote[], flatTexts: string[], noteIdToIdx: Map } { if (!this.flatTextIndex) { + // Measure heap before building + const heapBefore = process.memoryUsage().heapUsed; + const allNoteSet = this.getAllNoteSet(); const notes: BNote[] = []; const flatTexts: string[] = []; @@ -290,6 +295,11 @@ export default class Becca { this.flatTextIndex = { notes, flatTexts, noteIdToIdx }; this.dirtyFlatTextNoteIds.clear(); + + // Measure heap after building and log + const heapAfter = process.memoryUsage().heapUsed; + const heapDelta = heapAfter - heapBefore; + log.info(`Flat text search index built: ${notes.length} notes, ${formatSize(heapDelta)}`); } else if (this.dirtyFlatTextNoteIds.size > 0) { // Incremental update: only recompute flat texts for dirtied notes const { flatTexts, noteIdToIdx } = this.flatTextIndex; From 885e94cf5873dabdcc0c553ed43bef197f0879f8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 13:30:53 +0300 Subject: [PATCH 045/203] test(server): migrate database --- apps/server/spec/db/document.db | Bin 8589312 -> 8597504 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/server/spec/db/document.db b/apps/server/spec/db/document.db index 371c3a132913b09d17f1697f7082db6787b77190..f5cf761826cf91e826c1d2b4cee48fed49c75cf9 100644 GIT binary patch delta 26394 zcmeHwcYIXE_V_Jx@7;3u-YrS)CIM0?qCnW)WK$@TgjAA1I?0NVN($*ch-7yGMMVWE z2ip^Tmgkdaf&$lP1)k4}3O-a6(V(al3y58W-|Q|So2L1EfB!0;W$rmMXZp;UbIuI* zZR?P|cO6V;MbVR5?w1Oe7a;@W;d-`2UF0RNtzeS7)h%gv}7pKu$pX z3C#sLzJdHv1?xXW0vz}hDPh445_5qNPUG1Gzq3rS3b<6k2``1l!v1XH@m859`_+i3Mz zd36_gRS~>3QO~hdFwXV{u)o5Gy2`a2*1yX6-~7`jB>eOVrSS_T9wNkbLa%U>Fq!{5 zpQ1jkUZ?s1#`IEi<9EnehqzW+2O z-+!8}XeD0;rU=DAY!Pagb}1)mmT3w(9(}A#(Ik^EGZ(4D8Y>(uLQ8Znb9>d9>TA_g zx%Hwt;VvA8MrK|yaG4fNeNuEFQeZ?q=BgeyMl_;-ExsuREG`1x)wwsq(qGG~z z^hC-MotU4wAhoqPdU=`jl?xMIQyrID+n!XpJk26~?!rW*<+PFEX5%%rS%=^c#GTf(d3GfT@8+X~Z@rGuDH2xT$X z#Yfay>gJcEtXByCJaC0M?rg!m-teTtEl(sA-Ep2JM^sfAJ zBf5%q6C&CZ{qZwdZk1Uk<2&`L=CR56F@~-vhBZZJ` z&CfJvH$^9=CEBDf<-gsHDoG@KO6X+Oy(kosH^Q_BP^Y^tNhKY&y16Z#k)0`-MpAzI z0hAt07f}x2_M%P-|83ukl34O2`3!jnyuD2oB!9gZZ6c>lMInh)aZ5{6RY_}0dGnIe z*5;P_+Jvgoma6)?;-=0eHC3hcb#rTqo13L$@)P?}z6RxxMFiZl7fnXw9b^%h4x$^N z`ykp0-#wx7^^dZYb+px$)GbR$D)dhMqkC#g1IcCerSpqx%cUP+-*VDde)|xz2(0?Q zL>_TuGv{-Au;OmjE$XwX>-mTIPGO(0nfwA*U>(U4N5~@5f}Y+O*APNaLm@z(LVm`^ zxU|UnhU&cHifr$kzjEj7w{!rxFK5ubi|6~^79R;ZPolX1TGQ~UVASC1Gp zaunrL!n#Z7UKl9fO<+ct*mFtLfOt-;l+P=Cs`1j{TDxBV3`y?dz;C_C=t*_$DYXD$ z537~%nabx=22Y166s+H27zeX=7!+{)Gn4?&zN8f(p7)9Ju(|ED-WN2dQ6fB+uH)tJ zd7tf?RpnOJ@?V0;gpXDg1Z3mbs4pC-I*RaS|T&V^b;jE>O}` zTvu9&2YzK$Sy_4Ak|7g7`UuV$m`U*4{RUnR)iD=WQphNWu4e=qX$pyx5V4Ut!I(A7 zbOS8DpYbzVOqR%Lrif|g@L;oPMmYXIBFtp6*yOZLjG%OF7?JYz_cM=%(hbxhJUy9q ziaNA0ws#DkQcQ-6PRJYHV@{AkwTlor>N6DNJ? zX;gh_Q$|(Y-1@quRk*=R<+^v6YpxaO*H9r!MpiVQdK2m35iMH@S{*xqqOX@DbZi)< z$%-tyF?RLU~4rsCqw zs!zzwXt7DX;$lJ%{2lB%Pnw9truvTd&ib-pSTv zl3l>`vfqh|gv00pcR=wR`Ht|NupHRqY%qmK-Q?r!8kYQ0e&S2^33mM8%6l)L(kV9; zLiQLWdCNC!9f>N5-*LO2WhYT+DukV77bB!%9)@*i*+ud~V0tdK zWJr5nS#uCehR})BkcsHEqSKe;`QNh($*V%?O#j95Gv zk%tj?do#_#h_&!HGZzaLW=;zUW-dYwwQ!%*BJnw*2AFN!{}faUpCa5U>gC>T+$It- zAL5Kqw1e}3&5JlcdC!B~T}ntffRWYv`C!?;gHus(*UR{O@fM8&EZtln{Pzhi0|H** zRA4*IYvH+9_~4teZ2NhQM??FW_ zMO=j5uN2u(dr0w*dDkGbdAK%rcWCMShPkHH+|s25ruo#}JVLrgMS zcO*;S%Z*!<3?f%T`#s8)mWA!D3!;-FtFx+dtwzQ*Pjg!{nmVepJCl|sS*43^On#)P zvZA=KI>nqU{U~2`uksk8$CBrP+orr)&*m`um^AbNGShZiPZg8rWpgmOlbpS5EvHU4 zOF<#zYz3hhZpK_0=0k~a?t5WO_MnZ(8fGzt+Z@x@viw}L6zG^TBInDqVHm!LR;fav z=OrPaYkHe0%xtoRnS%2gDw>MR%7a_#gR8Sz%bPmGOkpO~(2dXttaq*||u?#bVqSW{e5?h#4hx9=sH!DfQeysc|Jo&OGH>qI3BIST0-p_t@R`3HDIuM=L9>S z;Gg6CG2mo6KoLO2&wl&$LKE_LGcD|77+PC} z2!?{k_bX$%o}gv=b>aznxBK@=_iwfPx6=Jv;r=ai|CYFai`~DA-M`nne;4B4f6#gO z_dz-r|K3Mu;omKECjMQI%kesKE1iyiSJSEZcO{*Ie>>dyHo=+w%BfJbUl|Xv2ZTWA zeMdP)UjH9u7$3>eGGg_*bW>ibZT$s|tj(Mf2`DKmOmC@4s zGKo}rL3h7%H zrXtmv-Bg&jplxnWtMrQ-V~&Yvv!!OoG}jdS6}eO8WE6K=a&uGL3rnR7@{bx-y${(5 zJv%9Xxhqt)=5{g#m>$(_3Z-3nQn^dL_R+09Dg{NH277_e7ck}-l>wsP7BxOY*L3N7 z2r)1N$lhzE^z9I`_evt2hQ=pVlOSb`nwRH4qk1Zl#UkfQx(v8X!}dkoCT;~+#-(!+ z+!zkAU$KYT=h%nXb!-c}h)rOpvOdfOe1JN@>|*X?Rx>qBE;Ean!0_mM^bvXq?LeE* z3RH&DQ3M);5d9VX5&aUqgWg21pvznvWf28ECk-lyuNL!RTcv1}w^oZPQfSC(o1dOu zpHiC9o?>_lwk{KA2=#eOlM-TTS}WSCvLq5-T_#oo)QJL2t`TQ$sS_v41vO$c35j)L zHmFyUg1lp)Ax|NbS~VSv)|17qlZ85)Lg&C{QiqdOu+F4@T%KuGXOom7OTN>hE}^7h z2P@SI3ah%YB3vR&)~cqdk!A_vE* z-ytbBNd7)vJ(Kcc=)Lk$ZMaocDs)yLf|qW0HEczY<+f>?a>2G~F^^q@F-h6c46i^+)ajU!0v4v3z;kQp@uB z_0my!T#0%Ph0q81^if=<{(B(3hWrdTyLtgj`I8rMINz>LqNoVC&#tZmy+eH{D}XU8 z$jic|wJ*;tpbVQievUwySu<;3eqi2b{*6(Y{KLC{;RDE7gU>|XZ6$p(nCjgj(r5Al zir=jv?*T^3|3smDxS-`T<@q}P0ySPtt!plCY6-?VxE9y&nd{cV1^27rs8+ewhu@q= zJBe$tPMBXvsiwlBB7T;9PZ58YGR#XV_wH%WT=#BD3UE{`{!3{v4qZm5y9ZOf zn}^zZg?f1j@-8Uus(r2eSi!r=s4cL*i61NPy@8KQ;M6OKWyD1Sw0rsI0;p{EJ$4s+ z7u&$S#68H_Rd?{~_-a0dpCo_0mtRSOZIez5J3b*5F#i#q3TEu*MFGpnZj34%H9ibT2%t3x(=Ux(N92BVLp*y~5u~LD9oF$)ruX z5P0o%em6z@6SmuxKCtNx{w@z~??#*o|FH1TM%@@#IZ4mL$&I=&`0xN`^2z~zh5^w} z6OH%`&etRIEPW7Kdigu>>6<_8HH*Dl1yKG%)v0aeY4v&WX>*gXCh*O0&BNFrl;5P801%NJD@y5)=RHXJ z<5B&JlYxw`oG20LR-EX57k%X0Q#Gqd2v5`a!ubS^!G9pn{v@=Vm8R*zD!W-gH)wJw zR15hHnrr>kPpcnMZ&EwO8S2G&m6=Jksj;XFPc&%!DE0+-sX^00aZU2Ijhc%jVq@`! z5!A}a_&|9IW@QRb%HL-ScT$w%u)HQm7)wG{w%`K?a|J{p+WjHp z4-QdS=DtGW5EG1orcPmx7YhF(r@`Jr;p+Z)EaE1kpgCZ-2}U`{CVcLz zY9uLI{SWmau?Is{|$UQvwQU zg+r?aZ2VXyXyD>1;X%qsz{+(v+C{s-!MI5VCA_g(m`WK*sJ|7XGrw^9Q`iDFZHHwkx?xtDI@2~{2I#0UZJqCewmJyEw>BrSjNQ@n+ddp zdx~>#GbvyMu82HDRKA zdHv&M(DABzBG^kcjApgR|JGILPSkO5qWB1I9V@Zo;tdbyXm9o${q zVnvB^X;d2&9TNQIMEt|LgJiq=FSy}|5I}mU;0Raw!^iIn_mdqS;^rg5Ay4d1+5Umx z$T!o)#BO>v;W=jFL*^smCebN2h>OK^afUcq^rM?agq7L7!dt>~!d7}VSWMzUO8qoY zX0b%YEuf#JAESX@AUBxBL*#_|rmVRYoe9>a1x@kNCD)qVTwWfNl5fsfSX`DV-R@SJ zmxqg|J(l9;teEns!pw#|t2t8oKrW6F7b3UJ+f98fHlAWpH3;BblIUn@zYd~dnP&`1?b80Cj5#T6vd{6Nl^Csd2` zNZG7XUm~Y^mv=G@pU-`HBPQHjw7XAaFT5Z0(T&w+n-!hg7kZZ?k z^T|+`t!qTHAhr2wIuFc7tx^7coOYK^>^JvW!@{jmu-Bx`8;A@y%M;AnC~_dy6baRq zK~XKyCiyLkcFVx17Hb4FM`*7bkltd6k&i`aCj=t?YvL)q>1n%MULUJ7wZUL1R{Ov+ znOZHSaKP)C+A)-}5WdLNw(-IouH$wUh zHWp}K4{0m5G%if7Z_Q3zkZdG$uBp;m(%zIjKdEDGT1A6&T3%VGZKBC+`P?FHHYv0( zvlOJwU6`I|Gv^wWa@JyPPsmk#6QM;SEv5^%X^v_puzBh?)FG-?<*UkIMHANRE10{{ zJ4ke$=f9*A9CKE8YRn5128~rgBqq+DJ@l;6r+S_-RQDW=&8=Z3*uGX9)aB}waJLN> ztDYlbrkKL9CDzM6dxd5-4WW5#x9)T{?=)BNR+G&!cXfxxyl`+y`*!BZE_JrMsCaRm zF}xPJytKHb9JY_gLs@&PmKjiFQ>1lBvHFT^ii{jW>nj>gH*Aq{8k-|}b-TtKH@L`s zGTm60^F)7<2Wf;iZtwIIv#h^mu>v(ZYDiH$#T=gIDW;n?Offxp$Mn^8aL%DLz%A>v zW8i!!Zl4#|X@h(`hV?7#Bh5H#C<^hBQll-ybBi?F99OSy)0oE(uC%x8>$S%Y9y<|X z(a}T4M_&=5qg_Q9IIj9?h|>*QCY;9Rh*{mLnLb_N8`3w*2WXvru=C9jH2}12V<)=D z$$$=sGL75;QKq58zOQ&urs!ddhtt>`*Q{=VtV)AHzGbcU6y1dv)^5+FB>vBn5-aMx zXHwr#{==EsH*38U_KkC#aG3IYrfhVSJpOj=Clq~{z$%yZ+6@%d16$T>ZGP%&)J8R{ z`d;;^>Y!?e>K@fvtpCjic7t}DAGMBdQx+xhcWJLS*QM4+C$>k%n@kzfZ?1*1qt;qm8Bw2@9I-Ul zufm1tOfO8ys41>)imaI@ossw7rH!9WwgPidTS`$PR36k$pvbSW?N!?X=#chH3ZF(= zj%b4^bos#IGBi;1B`h6j$H>s=dyCFiF{<_nYxE5 zE*N%X>LyV5`(UQ7TD~?*cPr`Ec9e7kmvl_ScD2%F)5_{wvF`~Y<=$*vGD-d{PtMi- zOle)7DV}Jq0%CQ)X`${=G)J!{s$80&(Dk~<;qCRhz%g_z`V}2Pd$6v!36Imu|GB&f zET#={K3n#YDp|2XH-`$mVlM2Py?xUIw@UB*IwLIKs8b>4A>tb-Udi~viyL(uOFoYM zLfft&p6MFI1rNB(`v=wO$+X`CsBet-E!R}X@G<&VN~(Rt81BW+WST(LjTEJCF~a&^ zJLvqux>;B6l8HiLL$Hsp{QPEJf(j{55cSx-K=;}h#goeY%5BP(%0#Yr%OLK{qRqH3O^@kzA>r1(%K!eDj!UA~({}-PRyUKP?~>!s>J+4$ zeomJ}>9kCsg6fm^h%MMX;qT{lkHrbza$RHu*sS`e1_)r0<|r5wsm~aIw8og_j!1pS zKmpDgX@yHs`ez45jWEG$(fX``NOOdIb&P%-Y4FZIB0Abuc+>R|9En{6>u2aA{0C6+ zKG$^9BDY<3I@~y0?+@GX310qqh91!asu2+mXJ_m856CgX6a)LN)#nUg86F*OYrH8( zof<9$T@Bmk=mXrxb2)R4epf8Io*YL&??KHZ*p{ih7HaJJbS-}mzk*-P$MT_`NeD&v z;BrJc^b=*)p?_D2-LtnVv__bJuYLxE9o78SKbO20b*ghLJsVHn{(00#b%{1q$M95- zON8ww|(P65K#mGOZ zZsh+*b#Yn#sJbz@x^nD&`j==rj7h@YDEb@~JB|5<*~zprNiJdhQ`9xpQNx5tirM`~ z(WS*T&E-S;CIU;?W?PJ788cH-&K}`JI&xaG)i%RX&CK!bOY1qPb`9__Y-ws8HfK|G zjBS>qikaiDoIT>Pv9`Rds`H*Rd|D>To%Q_D3CbqG+bt4fNGiPH9tBgUkLUrzi|5BvPLv1lSge4HBvc$@xI445d0P zJ)ZKC(y2DNRb}|pms|n`!G;_%Qr;14Si>vU5$uwh$#f3vtk92z9n%akEKFgu`1WefG-T z64|6uA7W&($Z+%quU@r=MOn;n=BVM&0Ays0Res|m!@~pjfXK)gx$9$tGeje7B{GR4 zgitLW#*Y61XVD8AGI{u5o61-I`Yd`}gYzZ@sJn=Q+;>-C|3!4dZNBvHJ6;ZQmclM; zZcpxkREp968dji{p561W?$SGdY2V_0eBXBF+QOdW-?K6FD_o1&+_+S~)XAY(Ck&`lg z4q(meyP^R;Ij%JfFau)g+U=b%XR(g~uq}|6mlycl%!O-;h*ZT^0xJWziEFVcut1E( zia@QRM|D>C87l%`6?%lNf?cQ-GKFX%m@m+Dz|s#^H1DF6q$fdNAHO4CBgW4Rfg;YSu`Da>(|i?o&RvFK)Pp z%}|VY4#PTH#bD{Ec8wnd8^j4?y)QGk8G3gzG~S{%bbU0mAzTN2vn_9puWAnVTy;0~ zhzh{Leqf!KV-j12;~Ql*;lla2s(+a;sL%68n4whiu zhN&2)VF<%89fJvj8G{8wIEDxeRt%9CqA)~bh`}%e!%PgbFwDkqErvN5Vll*Fh{rG& zLjs0G3`rQ0F{EHf#V`*;8isTX85rhcSb!lDLl%Z?3^^EbG2~&$$FLAX0fs^hHVoHc zScKtv42v-=!BB*u7()q$QVeAn$}ud(P=TQmLluT<49hUoV5r4VhoK%r1BONnO&FRn zv|wn((1xKMLkEUV49hXxfMErO8!@bupFG*6tkC({eg# zRnmt3O^CmU--&0$6XG%PJ@IYXm}#F%vgj~+1MQa+vh1y-{Cu{Zp#l{MpF*S$qz^oS zccphdfx}n>e=OI2M&&xqaq@vBcB_eFKgTyDe;{PlUS|U-cWkhilH()jDRe09k1xM) z)J66u5&yY-9`+RM9>YD*-EEIldOrVd_`5uNqy2H(sHgNl>wm^^m6ZM`{ZAh99>bnJ zU?-KFct6>Ivx0GZosY^dZnNvC$>@QLpyp1?8RW1hfAz8`r4ull~~3B2O_3Ix4qzeY}Y z*glB^&5L#k96RhmdJp-O?aRxepj*4cyZ3Kya%v6}Q*J*?oO6;L5-c>rq3i#$pdwiFX zGji^HjI5FM1Q;XZ2_Pf#1ZX4e2~bAL6CjPGCqNhpcR;!%UGfAjN*6tW-=*I@feX?F zPvAG{H&5VK=~qv{_ZR6GPv~dqXHVcKY-)PxDf?KGKQ0~jpn9cV_j``#ed7syC4J=yoRQ9W z0;i?Zp1>)|(*@EO(ia}g=hEk%K<)|YgeUZw^qD7cO!9QK^pWK0YU!wS)I&NV9q|OZ zrEYh?s4{w{lGG#hcrZ$%5}YsEC*VnfrT!;jr<}DsP?J-)QozCx?BA>Y{L4K zkK!lsH}KWM^WwMkW_mT?F3j7%VOpFkWVnDsL9Wn3SLpY1cC&o%1$!t3 zU!AiD%f~L-^`yTRDMGxiX!<^^qW2Jo$PeAs3D}(Jc+!svL?_Yv=w-AUJ%lzQCu&4R zXaTUKBa}tgqG@OhgprP|aD;SBfVW6T10K|a*c#cv2xc$>4Mxy|5!7G=IT(TMK(5+! zUAb{8T^bZcx;Pl|`(VU{!HC}mBYqu>_+=o%r&;=WAf{RR2{LKNQiOgXmclc%qp{1V z8WhE-9E?y5LP$RjM*J`sac(f;>|n%K9)wpLof#bEi@}J`2P2LTM)WcbM5xy|l0Lx* zoj2m+fk|k5;}@nfjs&ENA!f-fjN`w^SJ*L-$&2E*_&V}@l>^`R65b+o&?7j;A(6zP zcLc|D1shzJ}Uj$9(v&&e2Jx z$aD0LESgfi4-JfChJ3)+5v^iBpw|)NIasH4%#l-r9n(k^{VM%$`k(Z@^g6jM#KBW( z0#b~n`fhhh-w*u!cHrmffuAP^etsnHoaqQ6!=ND4NW#%Tm{ zpN4HjvP*uZ+Puhv^kXi|64tS0Ih~Sx{w2o}b`brt{){}}2$1b>I94a1S&HY}7u3c; zdXGa1)3nYgl`>lCulQAQRPmf#rghF&{83HuEwn$X>DCd*u9{+XAdE2f*if&*2RpXe2mP>?}sq6Mdf(#=5dihmoeX3(;oFOQ?FJ;9nw({7R|)QQM}_mQN;E|=Q82e)+$ZtGgeDshg310~KeP*#dV%SxQVu(24Y#*GyOxQT+` ztSf|@BM467Ph_>x*SUMd)x5!iiocIogq|nVN_DdOA=iz{z5#nyp$DDac|)*pN_bs( zTv#X63iH(u;b=-SkiMC&eox6DfH!p7Jy#|(|_rUQk+!DAG@bfCSIj2D(g<~Dn@8brnpk;lO@*Q6~ zk209x;~_l#t&@jO9#N|08^3X`A~8Yq5d04#N&fgV=SL(|{70rD~QR+ppe>>r)_2yYj?`Ee}`4aJuBgzEI9#Q33#8hk<45@W0DhC@3w zWAeSP`+MElEUrpeURt@#TD2h8>tgRHSmV35HQlX1KFSB?4CH-TNsDiL*`k(~R4hx$ z@Otgw&pEqp)wXow+m4z5_Z_dyp^CuLgo32#tj_AD%3QD8%%fn9%~OlJ;dq50&GRmE zRq@hgOP8B+mq&R&dNVqHxP5nC#O*skvDp94OiA+Gq&7=c!t_e-3)!P!jc@+ebT_!$ zH+LZK%k5hq6WN;BnxDGdxOnz&e z_j6C9;EfmWn(k$u79Pm^atoJ~F3ia-t1Bz6@b+{W1!sJzxTU+gzlF1gYKB`1+6vp7 z;!K6uTcqCs@y~dB1i8iCRY1ll{nETk9hKG;5j}rtcvNkr*M*GHFpR=oRsmW2RTA8? zP2{hw&q_?qu%_A~ya+zz=yxEuJB$Zk_r_Wnf?(HJ= z=UiR#^y2PPPcL~htcjnWl03aCE2qlaJ#};p>$;0Qo6iG3ItA!CXE0d3Uw;?{6D%#& zbQif>5ipY=tM@Z3qezd%soL)A;hdk+uW*2W-@v!E@;X~A(UtKX3H9DLjsKj#OD?SJ z2FEVe2i8xbm0g4LXsphQ$TT-+CR)9(GL3>UKE7PveVv++riUmd>Lw~@5C2q7Qd(PU zO^2zWG2eatv+MHx&rxK6Pl5&Ag=!)&#GQd+_7I=<)YN&+b<;a7^QL(F-~S=&E-Wy6 z91+U93o!4QmkWQH_Xu7I;q$8s(i^PHOOm{=8~tHMo_w&=LPhsNoX?!g`E(7)Xc%An z>V@XAc~Le~)dKGeLR4o?N{+R@K0YGO`$?fuWQDyU zD!cP=R&iI}Buu|@Enu#xt;yW5tTZLX`(3?JaK&yCOS*G0*Z!9dW3dZ)N=j{9_|kmu zHw8z*48Ny^P4iYLuEG**!!L#@AZk{*MdKIY&@YCmaQbtn9#&x-*8NgxL`V+o``kGh z8t%p?i;VpSE&S?8>a^+&e0%A3Rh?p`Vu>Oa zrk+(yhN2?pIG4xZ)12FokT=MX;CTQLaU-bzu_GYXBqDZXGFGu6Y;kd!@8;j`q)5oY zLC`pz3RftIRKoVvPCsB)IUC$J89nw3?EZ%}Xb6qS*Y%toqDLlU?GU0yAY%m&R&^k9 zFuCi>>$|Q8-ViaEeuX?Li^zznJ(iha$X%|df8D;pHd-Er?jd6@VH>@BWO8JTjec@u zazwa|t{$G;g)_&OQ*3nQ@N`VZ*HmnD#mHoQ*Mcq^nH+&lXC)(&yR5j^=;9ISuG=j1 z;*rT%H%4DSGC3TVZQ;mdQ%skQ&Kr^Ly4gbKj!d@VcFP)>9FEJDIWpOVZD<)ImmOd4 zw$bS$Cdc=@ZFK6$WNe|JQ$QDhO@VhgoMjOHvO|KYPUlswkP&WmVjO({hp_$b<7pba zfEc(2LcB#Z3J(hrnthrq{u91f{k?jP+E4YcYO?ZKWxC>!!p42U$+}=_E=M=_;Ab=X z79wk!*E7q1wkmqYdt;B%V}-#FhG?MSSA!wO`&#&)GjYv|mL7$A=(*h*UsZDDG5%37 z!tim|(4l>VGV76zTC)tnR@NjbwY$ zyA=rfNbmP!|D1_y!Y=PYYPcg$pB&-+&nJu`EuPJFJ+NU3FF|m=UJu94I(@9(4;YPt z2cGgZJ(RlUX*R}7tVa+2d? z3L0~3EuG#k7L0-&UT1FT?t;eeoT*mtXF*1h6t6+`-78^zGVW5B8P)3j_U|atV-;v^ z_l;0|-Z|6i{r1$KCv~k~4c#l?(0S*z)^e|>AxDuEuWfbRH^8|9JvNiNp4_SX-l?}P z_qx423MP03Z0KI@%0I~S#!oM+_pye1{qr58V1n1ly6#R-N!$+w-C@xx;5tsPit&CD t^UpcCR@9d64)=Odd{8$9giwQWR&`W#Mnz+MRaSPT*ArZ$;D#6A{|7JfJlOyM delta 13264 zcmaia33OCN_WrB)s^9CkcfamVXMvCegvi>RJ%EseY)C>92us421VR?FCqNPvx;qM@ zEJ1|JI649%Zb+g6rH)EK1r&sUIx>SxP(~SW0rzqJRj1Jb&i|Z$_xawb_tmXi^=iF! zt2}*cv1i*_+!>~ZF^t*GT>Wi~PDEkPlx){!N))MI(5KnGNwGY`eNkBTT#sd#x~b~} zlbS|pS62>wj_FEx?!N;~2J3v==i2W=LW*r5P4u1esy<5kN_tr?(0-N&L1+USg4JKO zCrDZY`d)&r1+s(HAA!8v&_TpNq-YO<6B&{N#@~(_S>-%*-Hzh1I+qEDNSnz9gLsnw z>>cPVH-L$RzB|wW_bUv85}eq16I)l`Qr=uymA$wszdAK@O2ZPVt=zbzwKCRWc;G~~ ztf{naVP$zUj9P)FqS0{s3KRlaw{iLi{85YX6)o)#P8>EE)i<vw)m61rmDW_X zmoHK>X&!O?N}y*3?|>IIlcwmK7%>Ki6(Jf4=YGXOu;&5}fl(__63aXT+oszFk~>$T zVI{ESkRoEL|2D|qDFnmX9fIV4ZHLaILdfbLK0@%rN30q=nAyqbYqdUYl@_f$qGZb# z<+aj3rB(>-V<(b1eQY#~8QRB^*;dlvNU|k{f$ob-4d4fDrco6NCLt%U=PJtDr zIEXjxv2it%D#~JV zrsl=9Ef)PmZ2pqsoVI04D)XxvoNxP?nDpfGoSfP5`I!Z2&eQ!&Y;{`x!t#t|MLAi{ z-u_gxrY9B0HkVIoD=2gx_A{=9(;6$LB@|>QR5Umb`kA;QH=J3*TF6&b*x)14UgjmV zgX5>8!)ydw$-cvl=B9DWxZU`7QgqeiG2ds_KLxu~2G*TR5KJeHMtnAwSA;GRw# zP4;%;_sv>HVRlV%^Rn4lF|!srUm<_{3$8@i!rg?3^j92((FPdxFm8h{O4%Ut(!)4A zf?L4u0ilO-z8;)uX6@(#dJ(OMliQ?00xEMS?VU3jhgtwbQ=;zF2 z&M^9EeV5*<=jh?u*V=2^R;_xoW`70#xlMBH`@;T|8c4iSvukidM*9-ZET128cO&UkX9}0uGO{VjkW`C#%-a?Ka1GZ zxarli_2lS7rtwi+JxlZGF)asAkEx3H&)f_{HvYrJv0y!Ga%}4R6vOxz4UrhHn!Z-J zSmL@JQk6Si5 z)A<%TYvXSofLEcrI54~TRnY0;$HNI1e=9~Kq#Y#K&F`03Z7%5@$FF4teKZqJXLmsN zNe*N{+XQ}JxL)1Q(HVb1WqD1*g0$G8oTA*g@}}y#S%yk-Ci44IST!6t45% zR@heOF3PBgNp+^elk54tEW<#_20kvJe(MIlCLgT;Qy>4e{to^{IBtFkou-|yeIN$@ z93SDYhv;+sT|Amc_I$?g;mx!~{q@lT1+PeU!SVP&>~ymbx!>j_TfXG$5H4qar4@Ub z9}2F^{Cv3YGC!AGxXj11ES6xB`nC!8qoGPR6BXl%i67;P85NTd;Z7bKpEx!)esrwcHNq9+a-qCP zE`x;)bAjZO0Tlbb~6fsH=Z%CcfQ*nPqh&5H)|dFa%!PBTQxWmjcYT zJ;F_^bGrnR+hryZxRCjr0nOtJfyiESGR*BY$3SPVIUWx7nrE{p8~Q#sPlMW1=EtGp zGjo(_Aa*-hc*Y^vEdE}L^7@JX9!wUTHt%?OAXmry9X*YtQYnvP`D4;8(g-O;62$N5 z{CWx^I>m&c!VzJg@QAQcSZR7ds1@c2lZ8a#CIRtZ@gMLn@Q?AE`3}Aje4XM>h~6lU zg+&|1p^7}r|F2(MzqmDr+_Ot=S9f@|}+m&n9sdYw!zbMDxP* z#Dw~$#G2T4=SvX0Ma(j!X2zIa7N46iqiuF`dGoZOqoi?*Xu@bOEPp^;5i@=IGS{S* zOm}8`PQKug>#1}>R$)!m?3^X;xFyCu|IjGRo>e|=*247kg=I6G_mE+K6+gh7oj(A= zHt}ZSjzAlDa$uKO5_0od_FeWQ`v&_e`!Y+|XV|CMN7;wjzp$Iwb?j=^%igBvuuIu` zwu)r;h@TPxn*irVH!IX z++*b+c;$+yz{LxqigHNb1u@czxeu8An@v*g=0YioReLrslg1ljhWN0!PP|QhP^{9* z#98|Dn~zGP6`@0D6c$L5kSmM>9!bk2jQoFEF;Bq?B>k#1EicN=%3nI!73(}fR#W$^_Q?h9ZA;67*83ZIVRc32(uMBIhKy-R z&K^Hg;ZBZQRy=uHa&t{-P>B{wQ`IkTo;+nzVs%+doZUmdSEYJ8YJu)&*buTZO1f(u z$^cWhbgx;y8@EZ-Qo>_fyCpM=RuRu^yAOfq1<6LnyeOT{7{m@|CZi=t;pEfuHhHO> zBS%P=r2W!rX`bYkFqrdg`^dZbwi$?3Tx8@l+ZkcY3N(tii*3Cof-Eu@f!Cu_|6(Lh zgSjp_7B;%%CrP4Po{3m|g=~nC%UEoxVuIn`bU7Ss@$yV;E}*Yxv*mDdZ@k2xwUQyv+{fAmMl^up#s`U{vW$`A0HMad^QR^>N#@X*P1&E-uk5oJqTTIik({cM`T49hC7 zD{m^TiCA3UR9o6YYVFGADcnfr7P&CwTTs7^u)Mas zB70f9JNMSyMJQ`HcEiCbvYpgbD_cw`9ZU_%Fo(sP@Vny&K=Od0bV!%h=k6Ltt5F!v}g8B+Hjf8o=>pM2P(tUyq_ zQ_!Gfw-5m%LN$rpe@Jx&{PVt*H%Ee&r z`9HdGMf}W`(!}bP*wV&Wv;!hOR-9ze$I62!#1$AMl?%&eW@b)rODijjMXPZz9d~nm zY&b~g6_xz{iDDW__wy!;8b6%9mn}e_puK3dyar8Tw#ieN@AXY0ezJ{%FpO5JL-Qg@IZ`m3mlA9 zMnip-DuX3fnTc<~VYT2MphiPqsA?gr!_+(M=KXYA%6732fyJe!!%UZ&MmstD&804i z;GPgQ^DpL$=ELT_<{jov^Gb7_d9L|ZbCP+e*=}Yb%B{|o@E7!J+Sm$q23g@&!`L|P z?~G+ha;TdyjmSIJ*J|qI5uHfcAfK8OqblMWi2xnH#DbLXCx##AA+}H z)kmp$IkQO}0uZMf+LE%?irn~^yoS~#c{aM=Y*M4OB}EDG#The`8}er4IikrIajIR$ z_$*UQr!+MZhNq~jU~h^#n*1w8MHq{2YB9P?CA(O)u)c0mO=)v8`6XSA$5>Wr->{ZR zc1UT~MIPNpiAX;Y#-ok>CZzRO-A>j`RyzX#R-P~C(kXPAe6yazjhBOE-fYu%NguP{O1;u6(r)Pi z>27$fL2YI6Tr#0i{S5_Xwv=Xd6g7EL7^B`bB{4IuxNdrOYpwHfva&@zh_JYp>4fxU z>I%r8s2k*+Wvc6+s1{85S|X(7YtND|^R>;?e%?Xunyw8*q2_m(Q1;KSFKdk`YaQix zK%qOJV47wpy@i^9(IB`oS98dtN^4qBX9ONJh8lnCsC!EvC7Zw~(w;_H&{L${3~v=_ zlKly33X^n=(r7$rB6y0mU^1v!`?rIyM=U2l4qAiuK3=rs)}oE6!Gvwpj!S45gzuKa z%;+nB6)&iS84AiHskdumSgbFjb^EswTo`Ovp?&7}~(bSWUBbUOdJGCI_>Cja8twYJVi#`rR5;rdD@L5^Wd3P+F2QCk=+`+Q;$BUHk`ua0lJC(H5IXA5XEb%J(F7sz@a={Tj@_$uu+9?!X5w1pkN*%@>}jL2GAwWu6c zZe&AA_iNfD4!!43o3-ViVpw1j`zYCBg@<1^G@iSgVN3)4(MfnZ9jV}XT@HiZJ6H); zK4A|h(i_?jJQn6NYl*2>Q)$mGGiRGeo9)6+LZ5Irgo|g}o3`I(XHj3j$p!}#q+Iy$J?$ab_#SPv&1bYzto#ymCP+@WElCO?TmPw5&2$TS z%yZNa60n@9-MmY`U-#(^`h4ng8OxRG(RvWqtYfNsKIW4Vu*-L7sR2DcNC2x+?6ERj4x(=IC z^gNiEqG#IGquONJcF|lo`!o9$7udpK!hG8t@>GsqWRupQVT`s^o2O;K_&!_ccuiHm zQ&N;@ZJlC~f0X;=qiC4iEpIh-$R4>;&X<$q2-z%Mmd;2$(&N%r^ESyNRZ96%k`w{v z5`8}jE!E4gpms6>b!Nl+SPmwCE!RyP_Pfc+&Px4u6bKHf)^A1dESBToH!Qm#(P^Z|%1Nb7DN(Xuh{ZCKv|21@6?7ki7U&AeXtx$c!s&I*MF6ePtWxSx>KjLB%$cY%s76ds@ijpZ|bYhGfn&T{U0x8|`64R_sJFW;snPkmRiRGjrM1iBbLvu)3#V0QJ9m;5MHV{T7m+J-Ez^+J zoYkBbH@hMuc1A-@(7U8yzNI_Te|939n;1PuyH`D{4pExqV{)XlR6Ht1n4A2&m(NeP^0GW|?M<7(@4}G!9 zBEqbvMGJ}{gV$L;V!4^jRxov1Hp13UOK}jpRvzwOT%~WNkLkkNzpP5jz`W5C9>gsZ zi^ME3p4G&`qE%$fUz$HKf3V5jlMmfqjA8vo%Ta^vW{lS9`K5J5mBp!Tr5VocPudR%$&&Y~fTo_crzQY*HD`zc_ac4{_ZLG-)N+O5u zv!vaEn!(g-SqRm=v~4Ovd9;U1N4P(%qo=rZ=Hj2LD4nw`14zbc z%Yj_~{vyH}YnEjhad+e6Fuof_kVO&JAO!i5R!rI>t$Pr95N;l9ZI-x?8L6zkehF)a zU4yL?Q8|4294aMQL#!{$9B+D>2rg?V68>R&%e0@qjdoiX;Z6Ask??f9)s5=NNAcF1 zLb=Us9|-x@M`2ICbqKtZZ>=U{3#{uAOq^p4CVkVaS%{V)y3qO~YY~2AECEAeVf~_V zw34`HSx+VT{p^i&lYxK2XYhV}KW@VLcr;eHi`-G}PHq>M0_INZQv-LJe5Ml91k-@P zUPPELj2A-rAI-xREPT2#)xPB*l8AYubs`J1c3N$4@+O0a&_}H@{O2L74(*$)5_pa% zq14x*@KAYN82~+-tS`W^Bg#$CeptB+R)f`m_|4XOXxL&6A+%SeOW2&lR8xnX^^F1I zdGV0AOuqKdzTG-nz}h{`ap-(s>4pc6 zDjIwkYNg&c7D_f-gTeKLb#EZ2U!SlFncN2M5fCn0$C32QRx={EUa@Ag)36dBpC(%C<_^$Yzz!cE<$2LTyfE=c@U9cIG}#45$BiCWLI(1s=0EO+c{_q+;NK$ z0^6uLL_QpAV6I;~iigX$7>BMZk9Wbr3C8sP7$q*gxN+5Vd2+0C(9N)Yq7m9}&yf6y z#@-Y>8-+8_*Q*YNZTaE^sPPy%U^$1JQ1O6aK%&>UiSSnEF>^EE#=P z-H7}WR?@rHh~rQx9n!|*)c)VyX$%0gTeLyqM&o9}Z!~-&{Ny!sQ&LPZTL;@#8NpIa zLQFAwMm>yH!80GwCeXf8zlQv?YAA$%u4+(xmTr%NKT)$t)`P~&9CtHcMZI?PYtnjc z!$?X5x?mzzyNuhT`ah3##gK208qZN@_(A`??Mfp1j~P=jejII+!Q5-ak+fc8I3kNr z8JDP+MS_Gcqc7$y@T1$3E*zQv3 zJTVvgMpA=!kbsgkI$FtsnzF)%nn}^zbaa?h&1)T-EjQHI`#yZ`WI~uNcviK!XAz`Z0GNi2+v-T z=LUQ^>wcGu;nFUfMiM{9WxPq6$Bbs7tx6ik8fD-`VIu>3mR zD{efG2f=_SQRIz6rHvWsCx)`LfApV*P52Kz4IBNaEW#%j@y8}AP|QN-ZfXqOvfCyK zjurkS{mf0Q;%>u;n#Jji_Y^w0qJL0YIMsMCmi z`#Ii@OjLww&)-9}|N69Fd%HhTKSQ;*1?^X6ISeZ@T9(P_)@?g#qyF&7o*hLhqmi*yFY07F$uAiJt zm(Hxk=y7PMv3~VEEv3 zp90%1*m(e6P+`Lby9vV1o0ZkiUa(U*D1s=Q6u}fB6rmJh6ay#*QiM|sqKKe~q==#z zOfiJwCW@gH!ziLDZl)McF@j3PmbK8pR}vbczg$Oo}XuY>LSgw^B@@$f3xkm`agHkxx-TF^yt6MIpru zikTF%C}vX>Q4~|mp_ofCk77Q>0*VrfQi?K)g%pb@$|)98R8Uk>R8dq@ETO2OsHLc* zsHbS4XrySOXr^ePSW2;sVmU=CMH@vs#cdR~Q`|wZf;@X+rC4Egyzh9QUdfTnSY%n- zBeqB2c%COd1Rp}P@e7RpU;S(Sl73$QK>vq+Qa`F6B%vvuXgUGEK(Y%wOA+@DK1N=e z=HXetvmlHdPWOBy!J`X2gE5ck!h3vyC*CEnzo9DjJBCO-z6J!NpYdz};XcnhWw&ts zfBlF5{izoJFHoEZY!C4NW+t+>Ew(KIX0vTGWk#{Kjkb*erqk9*nK0J2-nO0wMAo*} zwl)xTFWt7?vdQxl7aYbKKN>$$y&`M;VEoV@ZG3NhzaKnE6!3Yph!zjD9v>lH+dNh_ zSc$v-&wdy9lM6dMzo24MC^Lk0cpM(3nr7`-=~zhv2J2WsC#GteiQ^8(9sQX&jyjG8 z0!JK2XcSG=?r1l~`(@f3ZGos(M{6L}GRHD9;Ze^>!BDnQb@oDgAq|MEeY$-*+`ij0 z#L)lk=X343`VW3S$DTu;-R-%=V!tNX-sKqp$|g_rVEU=FN@#QT=V04z+ur~24q6IH z-$k!W(bgE*)^LSIRt565VgN=@tHN0bf4rNn7dcb@uja zbDngb3_-k0)aQ3Zw3NyINt~ajyeNX;ymI!5?~HH4+jFT zJ6{h3UUR+{2)yciH4tzdavlnV4muA80zJ+i^4fErfk-*vJP@E>alQgy?Db@D^PKZY z{60^+h=aHw4xaDwWW%=I9;fQ~%<);jtm9M1r_`qP42O$5J)wh~UCyq6j<=m}2Lj#B z?m*zQ^ECJlc!tnxZ?yWIyPUiF1CG;<(*c1~)OB_6faiCe`c@8}^w`l-@{g0A5fOrv z%sj^JVnFKl4JIQldq$w)+-7QtH*;nG|39oa=Sd*@uXtWUgKx87b0k~Z($Z8#&o|1O z9ql&~ZIJwvCxYzz#&aj1F3q7w;{TJjdv1`nuaq99r|8#6tf0hoN$!9O-#p_-!nXA0NmSVxKL3S^53qf7u}fSLiywCHI-4~ zI!&d}^E1s+8G60k977NWOcMStzJTAuhw(nV6K}#k+=xqX9-e?l;Q?6Y ze&YVao#Bpf`@!7p8?ZUb`z)4jm6A7K^hWVIqW-VlU?A^Sv*{tzdecf%1DrtK20ChQ zs6FS&8;KJ)5^vo|9KVq`b|dlTjl>%PV&$J5)_L?sks~(}hi@cazma(DM&i{Qi9^>D z_Gah7>zQU}59D*+#n?~7i=4NSC|`Rl2*#Pb@o=BRJC;0Y@@~M68uV1++VxZ>HA5i#q5ndI3!6_BzR^2p`g6^hDoaC>Y}lg9F_*>Y9z9 zqQk(w-y13>yNl^n7REdj&MokTl6_-*IW9q)%Cw^}1}yzIW=ec#S^eC`)Pax^++l|Q ztn>vz-%4Ku6qWks`L%?>;FZ2XaJkf%7I@7q^OZtDnJ*lUm-*&{oA(VQSqps;xIa%= zy~1aKk(E9TQogoe@<@g6Dc*0x41&m0s)LV@cNMdr(!&pL2w7F@~$JVtrOVCWe02AUJo@8v^y~d~;w=qi>i$iwSV* zF2ljQ5@>udoYQ?ybNpymY!9G5gMz~t-HrJfR}g5S#-bE=kbk$3BesYgopZ6Q(ST}mjmv@R9%7B_lUov zgo5VtP4b6A;PVjL7_o=F!EleyR|eM5j!;lSJL+M=VXtJSZd6xNF}I>Weg9ibF?R=` z{k8ngTHlgCC1Mi%CHHsiE$4k0o)7N$&0kW7zZ}W2#awkD3(N2REGqwe!8op% ztN63TiS+vy{qK#&(M>I_4ZoHI8dv(iHyTH~nZKqce(cvtyX95Ec-pkH0#Rc=@kdcl zwO{Vx{+Gf(<>Fk$T*04jvGfD-|Mx~aOYZ(oA5K9hsdv-&9V>LgPrbeY7J98`nNTIn z74n2MAzp|E*D2phaG&)B=^7P zt2IwCy-05}J;A6SnMzFzUV}rqC-jH)Xzc|pn;wFD6+?bT9xJ^eRf<vM6BMxuSIPUb&lBOSGe$Ljh2;GMUGmv^2b-7~adl~`W-+Y64+9T__ z(&5T%V-V0o)PYKEQ&m#yjK!0eRHsc3h|m^l=$h28ie@7Q&Sm>}!>(L=-QcRMo2p9- zsOvw!T_0mHL++RkNol{v*WWKp9gM{-U8yn?IUP!Sts_8-vWY>}iAlK?jcHW{)2_Yp zAJ9(c+s3XGD0oY!>%i4BX)~O>?30vh@0bOI-SNdWT@wI3ri8)Ehx9?pwRh(GMFdw& zu@Z@Upx||SkDd16$sAkP1sm5;Z@2z5y;t7*wJt)~CEs&gb5}H!RMEQpfA=2~?LRbS zK{=h^Enm>4=)PjJDY@6&eD2TCL?=t_35Yx_^=IIKCoj^5p5HajKZ=~>z$d=n5nVsf zEGSKvLZRnVdWU@5rR!}Pfb;h`Mrkw6k5XR|0^NU J)s8Fd{{gPW;z|Gj From 301f23cd2d2f73e63c388e13758e438a804583aa Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 13:38:06 +0300 Subject: [PATCH 046/203] test(server): clean up search scripts --- apps/server/spec/search_profiling.spec.ts | 306 ------------------ .../search/services/search_benchmark.spec.ts | 4 +- 2 files changed, 3 insertions(+), 307 deletions(-) delete mode 100644 apps/server/spec/search_profiling.spec.ts diff --git a/apps/server/spec/search_profiling.spec.ts b/apps/server/spec/search_profiling.spec.ts deleted file mode 100644 index 8099a322b4..0000000000 --- a/apps/server/spec/search_profiling.spec.ts +++ /dev/null @@ -1,306 +0,0 @@ -/** - * Integration-level search profiling test. - * - * Uses the real SQLite database (spec/db/document.db loaded in-memory), - * real sql module, real becca cache, and the full app stack. - * - * Profiles search at large scale (50K+ notes) to match real-world - * performance reports from users with 240K+ notes. - */ -import { Application } from "express"; -import { beforeAll, describe, expect, it } from "vitest"; -import config from "../src/services/config.js"; - -let app: Application; - -function timed(fn: () => T): [T, number] { - const start = performance.now(); - const result = fn(); - return [result, performance.now() - start]; -} - -function randomId(len = 12): string { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let id = ""; - for (let i = 0; i < len; i++) id += chars[Math.floor(Math.random() * chars.length)]; - return id; -} - -function randomWord(len = 8): string { - const chars = "abcdefghijklmnopqrstuvwxyz"; - let w = ""; - for (let i = 0; i < len; i++) w += chars[Math.floor(Math.random() * chars.length)]; - return w; -} - -function generateContent(wordCount: number, keyword?: string): string { - const paragraphs: string[] = []; - let remaining = wordCount; - let injected = false; - while (remaining > 0) { - const n = Math.min(remaining, 30 + Math.floor(Math.random() * 30)); - const words: string[] = []; - for (let i = 0; i < n; i++) words.push(randomWord(3 + Math.floor(Math.random() * 10))); - if (keyword && !injected && remaining < wordCount / 2) { - words[Math.floor(words.length / 2)] = keyword; - injected = true; - } - paragraphs.push(`

${words.join(" ")}

`); - remaining -= n; - } - return paragraphs.join("\n"); -} - -describe("Search profiling (integration)", () => { - beforeAll(async () => { - config.General.noAuthentication = true; - const buildApp = (await import("../src/app.js")).default; - app = await buildApp(); - }); - - it("large-scale profiling (50K notes)", async () => { - const sql = (await import("../src/services/sql.js")).default; - const becca = (await import("../src/becca/becca.js")).default; - const beccaLoader = (await import("../src/becca/becca_loader.js")).default; - const cls = (await import("../src/services/cls.js")).default; - const searchService = (await import("../src/services/search/services/search.js")).default; - const SearchContext = (await import("../src/services/search/search_context.js")).default; - const beccaService = (await import("../src/becca/becca_service.js")).default; - - await new Promise((resolve) => { - cls.init(() => { - const initialNoteCount = Object.keys(becca.notes).length; - console.log(`\n Initial becca notes: ${initialNoteCount}`); - - // ── Seed 50K notes with hierarchy ── - // Some folders (depth), some with common keyword "test" in title - const TOTAL_NOTES = 50000; - const FOLDER_COUNT = 500; // 500 folders - const NOTES_PER_FOLDER = (TOTAL_NOTES - FOLDER_COUNT) / FOLDER_COUNT; // ~99 notes per folder - const MATCH_FRACTION = 0.10; // 10% match "test" — ~5000 notes - const CONTENT_WORDS = 500; - - const now = new Date().toISOString().replace("T", " ").replace("Z", "+0000"); - console.log(` Seeding ${TOTAL_NOTES} notes (${FOLDER_COUNT} folders, ~${NOTES_PER_FOLDER.toFixed(0)} per folder)...`); - - const [, seedMs] = timed(() => { - sql.transactional(() => { - const folderIds: string[] = []; - - // Create folders under root - for (let f = 0; f < FOLDER_COUNT; f++) { - const noteId = `seed${randomId(8)}`; - const branchId = `seed${randomId(8)}`; - const blobId = `seed${randomId(16)}`; - folderIds.push(noteId); - - sql.execute( - `INSERT INTO blobs (blobId, content, dateModified, utcDateModified) VALUES (?, ?, ?, ?)`, - [blobId, `

Folder ${f}

`, now, now] - ); - sql.execute( - `INSERT INTO notes (noteId, title, type, mime, blobId, isProtected, isDeleted, - dateCreated, dateModified, utcDateCreated, utcDateModified) - VALUES (?, ?, 'text', 'text/html', ?, 0, 0, ?, ?, ?, ?)`, - [noteId, `Folder ${f} ${randomWord(5)}`, blobId, now, now, now, now] - ); - sql.execute( - `INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, isDeleted, isExpanded, utcDateModified) - VALUES (?, ?, 'root', ?, 0, 0, ?)`, - [branchId, noteId, f * 10, now] - ); - } - - // Create notes under folders - let noteIdx = 0; - for (let f = 0; f < FOLDER_COUNT; f++) { - const parentId = folderIds[f]; - for (let n = 0; n < NOTES_PER_FOLDER; n++) { - const isMatch = noteIdx < TOTAL_NOTES * MATCH_FRACTION; - const noteId = `seed${randomId(8)}`; - const branchId = `seed${randomId(8)}`; - const blobId = `seed${randomId(16)}`; - const title = isMatch - ? `Test Document ${noteIdx} ${randomWord(6)}` - : `Note ${noteIdx} ${randomWord(6)} ${randomWord(5)}`; - const content = generateContent(CONTENT_WORDS, isMatch ? "test" : undefined); - - sql.execute( - `INSERT INTO blobs (blobId, content, dateModified, utcDateModified) VALUES (?, ?, ?, ?)`, - [blobId, content, now, now] - ); - sql.execute( - `INSERT INTO notes (noteId, title, type, mime, blobId, isProtected, isDeleted, - dateCreated, dateModified, utcDateCreated, utcDateModified) - VALUES (?, ?, 'text', 'text/html', ?, 0, 0, ?, ?, ?, ?)`, - [noteId, title, blobId, now, now, now, now] - ); - sql.execute( - `INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, isDeleted, isExpanded, utcDateModified) - VALUES (?, ?, ?, ?, 0, 0, ?)`, - [branchId, noteId, parentId, n * 10, now] - ); - noteIdx++; - } - } - }); - }); - console.log(` SQL seeding: ${seedMs.toFixed(0)}ms`); - - const [, reloadMs] = timed(() => beccaLoader.load()); - const totalNotes = Object.keys(becca.notes).length; - console.log(` Becca reload: ${reloadMs.toFixed(0)}ms Total notes: ${totalNotes}`); - - // ── Warm caches ── - searchService.searchNotesForAutocomplete("test", true); - - // ════════════════════════════════════════════ - // PROFILING AT SCALE - // ════════════════════════════════════════════ - - console.log(`\n ════ PROFILING (${totalNotes} notes) ════\n`); - - // 1. getCandidateNotes cost (the full-scan bottleneck) - const allNotes = Object.values(becca.notes); - const [, flatScanMs] = timed(() => { - let count = 0; - for (const note of allNotes) { - const ft = note.getFlatText(); - if (ft.includes("test")) count++; - } - return count; - }); - console.log(` getFlatText + includes scan (${allNotes.length} notes): ${flatScanMs.toFixed(1)}ms`); - - // 2. Full findResultsWithQuery (includes candidate scan + parent walk + scoring) - const findTimes: number[] = []; - let findResultCount = 0; - for (let i = 0; i < 3; i++) { - const [r, ms] = timed(() => - searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: true })) - ); - findTimes.push(ms); - findResultCount = r.length; - } - const findAvg = findTimes.reduce((a, b) => a + b, 0) / findTimes.length; - console.log(` findResultsWithQuery (fast): avg ${findAvg.toFixed(1)}ms (${findResultCount} results)`); - - // 3. Exact-only (no fuzzy) - const exactTimes: number[] = []; - for (let i = 0; i < 3; i++) { - const [, ms] = timed(() => - searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: true, enableFuzzyMatching: false })) - ); - exactTimes.push(ms); - } - const exactAvg = exactTimes.reduce((a, b) => a + b, 0) / exactTimes.length; - console.log(` findResultsWithQuery (exact): avg ${exactAvg.toFixed(1)}ms`); - console.log(` Fuzzy overhead: ${(findAvg - exactAvg).toFixed(1)}ms`); - - // 4. SearchResult construction + computeScore cost (isolated) - const results = searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: true })); - console.log(` Total results before trim: ${results.length}`); - - const [, scoreAllMs] = timed(() => { - for (const r of results) r.computeScore("test", ["test"], true); - }); - console.log(` computeScore × ${results.length}: ${scoreAllMs.toFixed(1)}ms (${(scoreAllMs / results.length).toFixed(3)}ms/result)`); - - // 5. getNoteTitleForPath for all results - const [, pathTitleMs] = timed(() => { - for (const r of results) beccaService.getNoteTitleForPath(r.notePathArray); - }); - console.log(` getNoteTitleForPath × ${results.length}: ${pathTitleMs.toFixed(1)}ms`); - - // 6. Content snippet extraction (only 200) - const trimmed = results.slice(0, 200); - const [, snippetMs] = timed(() => { - for (const r of trimmed) { - r.contentSnippet = searchService.extractContentSnippet(r.noteId, ["test"]); - } - }); - console.log(` extractContentSnippet × 200: ${snippetMs.toFixed(1)}ms`); - - // 7. Highlighting (only 200) - const [, hlMs] = timed(() => { - searchService.highlightSearchResults(trimmed, ["test"]); - }); - console.log(` highlightSearchResults × 200: ${hlMs.toFixed(1)}ms`); - - // 7b. getBestNotePath cost (used by fast path) - const sampleNotes = Object.values(becca.notes).filter(n => n.title.startsWith("Test Document")).slice(0, 1000); - const [, bestPathMs] = timed(() => { - for (const n of sampleNotes) n.getBestNotePath(); - }); - console.log(` getBestNotePath × ${sampleNotes.length}: ${bestPathMs.toFixed(1)}ms (${(bestPathMs/sampleNotes.length).toFixed(3)}ms/note)`); - - // 8. Full autocomplete end-to-end - const autoTimes: number[] = []; - let autoCount = 0; - for (let i = 0; i < 3; i++) { - const [r, ms] = timed(() => - searchService.searchNotesForAutocomplete("test", true) - ); - autoTimes.push(ms); - autoCount = r.length; - } - const autoAvg = autoTimes.reduce((a, b) => a + b, 0) / autoTimes.length; - const autoMin = Math.min(...autoTimes); - console.log(`\n ★ FULL AUTOCOMPLETE: avg ${autoAvg.toFixed(1)}ms min ${autoMin.toFixed(1)}ms (${autoCount} results)`); - - // 9. With a less common search term (fewer matches) - const rareTimes: number[] = []; - let rareCount = 0; - for (let i = 0; i < 3; i++) { - const [r, ms] = timed(() => - searchService.searchNotesForAutocomplete("leitfaden", true) - ); - rareTimes.push(ms); - rareCount = r.length; - } - const rareAvg = rareTimes.reduce((a, b) => a + b, 0) / rareTimes.length; - console.log(` Autocomplete "leitfaden": avg ${rareAvg.toFixed(1)}ms (${rareCount} results)`); - - // 10. Full search (fastSearch=false) — the 2.7s bottleneck - console.log(`\n ── Full search (fastSearch=false) ──`); - const fullTimes: number[] = []; - let fullCount = 0; - for (let i = 0; i < 2; i++) { - const [r, ms] = timed(() => - searchService.findResultsWithQuery("test", new SearchContext({ fastSearch: false })) - ); - fullTimes.push(ms); - fullCount = r.length; - } - const fullAvg = fullTimes.reduce((a, b) => a + b, 0) / fullTimes.length; - console.log(` Full search (flat + SQL): avg ${fullAvg.toFixed(1)}ms (${fullCount} results)`); - - // 11. SQL content scan alone - const [scanCount, scanMs] = timed(() => { - let count = 0; - for (const row of sql.iterateRows<{ content: Buffer | string }>(` - SELECT noteId, type, mime, content, isProtected - FROM notes JOIN blobs USING (blobId) - WHERE type IN ('text', 'code', 'mermaid', 'canvas', 'mindMap') - AND isDeleted = 0 - AND LENGTH(content) < 2097152`)) { - count++; - } - return count; - }); - console.log(` Raw SQL scan (${scanCount} rows): ${scanMs.toFixed(1)}ms`); - - // ── Summary ── - console.log(`\n ════ SUMMARY ════`); - console.log(` Notes: ${totalNotes} | Matches: ${findResultCount} | Hierarchy depth: 3 (root → folder → note)`); - console.log(` ──────────────────────────────────`); - console.log(` Autocomplete (fast): ${autoAvg.toFixed(1)}ms`); - console.log(` findResults: ${findAvg.toFixed(1)}ms (${((findAvg/autoAvg)*100).toFixed(0)}%)`); - console.log(` snippets+highlight: ${(snippetMs + hlMs).toFixed(1)}ms (${(((snippetMs+hlMs)/autoAvg)*100).toFixed(0)}%)`); - console.log(` Full search: ${fullAvg.toFixed(1)}ms`); - - resolve(); - }); - }); - }, 600_000); -}); diff --git a/apps/server/src/services/search/services/search_benchmark.spec.ts b/apps/server/src/services/search/services/search_benchmark.spec.ts index 53319ff9cd..c3ece17fb5 100644 --- a/apps/server/src/services/search/services/search_benchmark.spec.ts +++ b/apps/server/src/services/search/services/search_benchmark.spec.ts @@ -281,7 +281,9 @@ function printTable(title: string, results: BenchmarkResult[]) { // ── tests ──────────────────────────────────────────────────────────── -describe("Comprehensive Search Benchmark", () => { +// Skipped by default - this is a benchmark, not a test. +// Remove .skip to run manually for performance analysis. +describe.skip("Comprehensive Search Benchmark", () => { afterEach(() => { becca.reset(); From e40504b7f06adab6f8147f072d62d559da275177 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 13:43:25 +0300 Subject: [PATCH 047/203] chore(search): address requested changes --- apps/server/src/becca/entities/battribute.ts | 5 +++++ apps/server/src/services/search/services/search.ts | 7 ++++--- apps/server/src/services/search/utils/text_utils.ts | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/server/src/becca/entities/battribute.ts b/apps/server/src/becca/entities/battribute.ts index 77a15c2fd1..dbb6502113 100644 --- a/apps/server/src/becca/entities/battribute.ts +++ b/apps/server/src/becca/entities/battribute.ts @@ -202,6 +202,11 @@ class BAttribute extends AbstractBeccaEntity { this.utcDateModified = dateUtils.utcNowDateTime(); + // Recompute normalized fields in case name/value were modified directly + // (e.g., attr.value = "..." followed by attr.save()) + this.normalizedName = normalize(this.name); + this.normalizedValue = normalize(this.value); + super.beforeSaving(); this.becca.attributes[this.attributeId] = this; diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index ea1d20c263..0523aeb98f 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -595,10 +595,11 @@ function extractAttributeSnippet(noteId: string, searchTokens: string[], maxLeng // Look for attributes that match the search tokens for (const attr of attributes) { - const attrName = attr.name?.toLowerCase() || ""; - const attrValue = attr.value?.toLowerCase() || ""; + // Use pre-normalized fields from BAttribute for diacritic-insensitive matching + const attrName = attr.normalizedName || normalize(attr.name || ""); + const attrValue = attr.normalizedValue || normalize(attr.value || ""); const attrType = attr.type || ""; - + // Check if any search token matches the attribute name or value const hasMatch = searchTokens.some(token => { const normalizedToken = normalize(token); diff --git a/apps/server/src/services/search/utils/text_utils.ts b/apps/server/src/services/search/utils/text_utils.ts index 7528571f86..1993924555 100644 --- a/apps/server/src/services/search/utils/text_utils.ts +++ b/apps/server/src/services/search/utils/text_utils.ts @@ -282,7 +282,9 @@ export function fuzzyMatchWordWithResult(token: string, text: string, maxDistanc // Exact match check first (most common case) if (normalizedText.includes(normalizedToken)) { - return token; + // Find the exact match position and return the original substring with case preserved + const matchIndex = normalizedText.indexOf(normalizedToken); + return text.substring(matchIndex, matchIndex + normalizedToken.length); } // For fuzzy matching, split into words and check each against the token From f58dd12983cbdd1cdf47b9e72991b721271df4c9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 13 Apr 2026 14:03:17 +0300 Subject: [PATCH 048/203] chore(search): use loop to prevent nested strip tags injection --- .../src/services/search/services/search.ts | 6 +- .../services/search/utils/text_utils.spec.ts | 67 ++++++++++++++++++- .../src/services/search/utils/text_utils.ts | 24 +++++++ 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/apps/server/src/services/search/services/search.ts b/apps/server/src/services/search/services/search.ts index 0523aeb98f..97bfc457a4 100644 --- a/apps/server/src/services/search/services/search.ts +++ b/apps/server/src/services/search/services/search.ts @@ -8,6 +8,7 @@ import SearchContext from "../search_context.js"; import becca from "../../../becca/becca.js"; import beccaService from "../../../becca/becca_service.js"; import { normalize, removeDiacritic, escapeHtml, escapeRegExp } from "../../utils.js"; +import { stripHtmlTags } from "../utils/text_utils.js"; import log from "../../log.js"; import hoistedNoteService from "../../hoisted_note.js"; import type BNote from "../../../becca/entities/bnote.js"; @@ -494,10 +495,9 @@ function extractContentSnippet(noteId: string, searchTokens: string[], maxLength return ""; // Protected but no session available } - // Strip HTML tags for text notes — use fast regex for snippet extraction - // (striptags library is ~18x slower and not needed for search snippets) + // Strip HTML tags for text notes if (note.type === "text") { - content = content.replace(/<[^>]*>/g, ""); + content = stripHtmlTags(content); } if (!content) { diff --git a/apps/server/src/services/search/utils/text_utils.spec.ts b/apps/server/src/services/search/utils/text_utils.spec.ts index a5f1da129d..146f5cc0fe 100644 --- a/apps/server/src/services/search/utils/text_utils.spec.ts +++ b/apps/server/src/services/search/utils/text_utils.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { calculateOptimizedEditDistance, validateFuzzySearchTokens, fuzzyMatchWord } from './text_utils.js'; +import { calculateOptimizedEditDistance, validateFuzzySearchTokens, fuzzyMatchWord, stripHtmlTags } from './text_utils.js'; describe('Fuzzy Search Core', () => { describe('calculateOptimizedEditDistance', () => { @@ -62,4 +62,69 @@ describe('Fuzzy Search Core', () => { expect(fuzzyMatchWord('a', 'b')).toBe(false); // Very short tokens }); }); + + describe('stripHtmlTags', () => { + it('strips simple HTML tags', () => { + expect(stripHtmlTags('

Hello

')).toBe('Hello'); + expect(stripHtmlTags('
World
')).toBe('World'); + expect(stripHtmlTags('Bold and italic')).toBe('Bold and italic'); + }); + + it('handles self-closing tags', () => { + expect(stripHtmlTags('Line1
Line2')).toBe('Line1Line2'); + expect(stripHtmlTags('Image: ')).toBe('Image: '); + }); + + it('handles tags with attributes', () => { + expect(stripHtmlTags('Link')).toBe('Link'); + expect(stripHtmlTags('
Content
')).toBe('Content'); + }); + + it('handles nested tag patterns securely', () => { + // Security property: no complete patterns remain after stripping + // Residual `>` chars are harmless for XSS + + // Nested tags: inner tag removed, then outer tag removed + // c> → → '' (but leaves residual `c>`) + const result1 = stripHtmlTags('c>text'); + expect(result1).not.toMatch(/<[a-z]/i); // No opening tags remain + expect(result1).toBe('c>text'); // Residual text is safe + + // Complex nesting leaves no exploitable patterns + const result2 = stripHtmlTags('ipt>alert(1)'); + expect(result2).not.toMatch(/ + + diff --git a/scripts/electron-repros/printpdf-page-range/main.js b/scripts/electron-repros/printpdf-page-range/main.js new file mode 100644 index 0000000000..7c7a283d88 --- /dev/null +++ b/scripts/electron-repros/printpdf-page-range/main.js @@ -0,0 +1,53 @@ +const { app, BrowserWindow, ipcMain } = require("electron"); + +let mainWindow; + +app.whenReady().then(() => { + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + }, + }); + mainWindow.loadFile("index.html"); +}); + +ipcMain.handle("print-test", async (_e, { pageRanges, step }) => { + const printWindow = new BrowserWindow({ + show: false, + width: 1, + height: 1, + webPreferences: { offscreen: process.platform !== "linux" }, + }); + + await printWindow.loadFile("print-page.html"); + + // Wait for content to be ready. + await printWindow.webContents.executeJavaScript( + `new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)))` + ); + + const opts = { + landscape: false, + pageSize: "A4", + scale: 1, + printBackground: true, + // Only include pageRanges if truthy (non-empty string). + ...(pageRanges ? { pageRanges } : {}), + }; + + console.log(`[Step ${step}] printToPDF called with:`, JSON.stringify(opts)); + + try { + const buffer = await printWindow.webContents.printToPDF(opts); + console.log(`[Step ${step}] SUCCESS - buffer size: ${buffer.length}`); + printWindow.destroy(); + return { ok: true, size: buffer.length }; + } catch (err) { + console.error(`[Step ${step}] FAILED: ${err.message}`); + printWindow.destroy(); + return { ok: false, error: err.message }; + } +}); diff --git a/scripts/electron-repros/printpdf-page-range/package.json b/scripts/electron-repros/printpdf-page-range/package.json new file mode 100644 index 0000000000..5c380d9a96 --- /dev/null +++ b/scripts/electron-repros/printpdf-page-range/package.json @@ -0,0 +1,13 @@ +{ + "name": "electron-printpdf-page-range-repro", + "version": "1.0.0", + "private": true, + "description": "Minimal repro for Electron printToPDF state corruption after an invalid pageRanges failure", + "main": "main.js", + "scripts": { + "start": "electron ." + }, + "devDependencies": { + "electron": "^35.0.0" + } +} diff --git a/scripts/electron-repros/printpdf-page-range/print-page.html b/scripts/electron-repros/printpdf-page-range/print-page.html new file mode 100644 index 0000000000..04eb11623d --- /dev/null +++ b/scripts/electron-repros/printpdf-page-range/print-page.html @@ -0,0 +1,8 @@ + + +Print Test + +

Test Page

+

This is a single-page document used to reproduce the printToPDF bug.

+ + From 9bd78c5f837a566a0a3e4923d384e169d064de7c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Apr 2026 22:45:23 +0300 Subject: [PATCH 160/203] feat(print): display detailed error on print failure --- apps/client/src/widgets/dialogs/print_preview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/dialogs/print_preview.tsx b/apps/client/src/widgets/dialogs/print_preview.tsx index 4720349d6a..c99e63cd17 100644 --- a/apps/client/src/widgets/dialogs/print_preview.tsx +++ b/apps/client/src/widgets/dialogs/print_preview.tsx @@ -170,7 +170,7 @@ export default function PrintPreviewDialog() { toast.showPersistent({ id: "print-preview-error", icon: "bx bx-error-circle", - message: t("print_preview.render_error") + message: `${t("print_preview.render_error")}\n\n${error}` }); return; } From 98d718031b7e965f36dd02edfa175a702416cb2e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Apr 2026 22:46:15 +0300 Subject: [PATCH 161/203] test(commons): fix failure in locale --- packages/commons/src/lib/dayjs.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/commons/src/lib/dayjs.spec.ts b/packages/commons/src/lib/dayjs.spec.ts index 3f3d9e5dad..37d9ff9435 100644 --- a/packages/commons/src/lib/dayjs.spec.ts +++ b/packages/commons/src/lib/dayjs.spec.ts @@ -14,6 +14,8 @@ import { describe, expect, it } from "vitest"; describe("dayjs", () => { it("all dayjs locales are valid", async () => { for (const locale of LOCALES) { + if (locale.contentOnly) continue; + const dayjsLoader = DAYJS_LOADER[locale.id]; expect(dayjsLoader, `Locale ${locale.id} missing.`).toBeDefined(); From 3bd98d6a1b67e85f92a5fd6c6aca445d42f15fc8 Mon Sep 17 00:00:00 2001 From: Ulices Date: Tue, 14 Apr 2026 22:09:58 +0200 Subject: [PATCH 162/203] Translated using Weblate (Spanish) Currently translated at 100.0% (403 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/es/ --- .../src/assets/translations/es/server.json | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/server/src/assets/translations/es/server.json b/apps/server/src/assets/translations/es/server.json index 36ba41a88f..a08f131b93 100644 --- a/apps/server/src/assets/translations/es/server.json +++ b/apps/server/src/assets/translations/es/server.json @@ -200,7 +200,8 @@ }, "quarterNumber": "Cuarto {quarterNumber}", "special_notes": { - "search_prefix": "Buscar:" + "search_prefix": "Buscar:", + "llm_chat_prefix": "Chat:" }, "test_sync": { "not-configured": "El servidor de sincronización no está configurado. Por favor configure primero la sincronización.", @@ -242,7 +243,7 @@ "shortcuts-title": "Atajos", "text-notes": "Notas de texto", "code-notes-title": "Notas de código", - "images-title": "Imágenes", + "images-title": "Medios", "spellcheck-title": "Corrección ortográfica", "password-title": "Contraseña", "multi-factor-authentication-title": "MFA", @@ -258,7 +259,11 @@ "jump-to-note-title": "Saltar a...", "command-palette": "Abrir paleta de comandos", "zen-mode": "Modo Zen", - "tab-switcher-title": "Conmutador de pestañas" + "tab-switcher-title": "Conmutador de pestañas", + "llm-chat-history-title": "Historial del chat de AI", + "custom-dictionary-title": "Diccionario personalizado", + "llm-title": "IA / LLM", + "sidebar-chat-title": "Chat de IA" }, "notes": { "new-note": "Nueva nota", @@ -304,7 +309,10 @@ "last-updated": "Última actualización en {{-date}}", "subpages": "Subpáginas:", "on-this-page": "En esta página", - "expand": "Expandir" + "expand": "Expandir", + "toggle-navigation": "Alternar navegación", + "toggle-toc": "Alternar tabla de contenido", + "logo-alt": "Logo" }, "keyboard_action_names": { "jump-to-note": "Saltar a...", @@ -437,5 +445,21 @@ }, "desktop": { "instance_already_running": "Ya hay una instancia en ejecución, enfocando esa instancia en su lugar." + }, + "password": { + "incorrect": "La contraseña que ha introducido es incorrecta." + }, + "script": { + "wrong-environment": "No se puede ejecutar la nota \"{{- noteTitle}}\" ({{- noteId}}). Este es un script de {{- actualEnv}}, pero la ejecución se intentó en {{- expectedEnv}}." + }, + "search": { + "error": { + "in-context": "Error en {{- context}}: {{- message}}", + "reserved-keyword": "\"{{- token}}\" es una palabra clave reservada. Para buscar un valor literal, utilice comillas: \"{{- token}}\"", + "cannot-compare-with": "no se puede comparar con \"{{- token}}\". Para buscar un valor literal, utilice ccomillas: \"{{- token}}\"", + "misplaced-expression": "Expresión errónea o incompleta \"{{- token}}\"", + "fulltext-after-expression": "\"{{- token}}\" no es una expresión válida. Para buscar texto, colóquela antes de filtros de atributo (por ejemplo, \"{{- token}} #label\" en lugar de \"#label {{- token}}\").", + "unrecognized-expression": "Expresión no reconocida \"{{- token}}\"" + } } } From 3ed62970111dded74001865e423038b1d5c0c516 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 14 Apr 2026 22:42:59 +0200 Subject: [PATCH 163/203] Translated using Weblate (German) Currently translated at 91.8% (1726 of 1880 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/ --- .../src/translations/de/translation.json | 216 ++++++++++++++---- 1 file changed, 172 insertions(+), 44 deletions(-) diff --git a/apps/client/src/translations/de/translation.json b/apps/client/src/translations/de/translation.json index 24b3915b36..1efdafdc6e 100644 --- a/apps/client/src/translations/de/translation.json +++ b/apps/client/src/translations/de/translation.json @@ -92,10 +92,18 @@ "delete_all_clones_description": "auch alle Klone löschen (kann bei letzte Änderungen rückgängig gemacht werden)", "erase_notes_description": "Beim normalen (vorläufigen) Löschen werden die Notizen nur als gelöscht markiert und sie können innerhalb eines bestimmten Zeitraums (im Dialogfeld „Letzte Änderungen“) wiederhergestellt werden. Wenn du diese Option aktivierst, werden die Notizen sofort gelöscht und es ist nicht möglich, die Notizen wiederherzustellen.", "erase_notes_warning": "Notizen dauerhaft löschen (kann nicht rückgängig gemacht werden), einschließlich aller Klone. Dadurch wird ein Neuladen der Anwendung erzwungen.", - "notes_to_be_deleted": "Folgende Notizen werden gelöscht ({{notesCount}})", + "notes_to_be_deleted": "({{notesCount}}) Notizen werden gelöscht", "no_note_to_delete": "Es werden keine Notizen gelöscht (nur Klone).", - "broken_relations_to_be_deleted": "Folgende Beziehungen werden gelöst und gelöscht ({{ relationCount}})", - "cancel": "Abbrechen" + "broken_relations_to_be_deleted": "Unterbrochene Beziehungen ({{relationCount}})", + "cancel": "Abbrechen", + "title": "Notizen löschen", + "clones_label": "Duplikate", + "delete_clones_description_one": "Löscht {{count}} weiteres Duplikat. Kann unter „Letzte Änderungen“ rückgängig gemacht werden.", + "delete_clones_description_other": "Löscht {{count}} weitere Duplikate. Kann unter „Letzte Änderungen“ rückgängig gemacht werden.", + "erase_notes_label": "Permanent löschen", + "table_note_with_relation": "Notiz mit Beziehung", + "table_relation": "Beziehung", + "delete": "Löschen" }, "export": { "export_note_title": "Notiz exportieren", @@ -338,7 +346,7 @@ "calendar_root": "Markiert eine Notiz, die als Basis für Tagesnotizen verwendet werden soll. Nur einer sollte als solcher gekennzeichnet sein.", "archived": "Notizen mit dieser Bezeichnung werden standardmäßig nicht in den Suchergebnissen angezeigt (auch nicht in den Dialogen „Springen zu“, „Link hinzufügen“ usw.).", "exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht im Notizexport inkludiert", - "run": "Definiert, bei welchen Ereignissen das Skript ausgeführt werden soll. Mögliche Werte sind:\n
    \n
  • frontendStartup - wenn das Trilium-Frontend startet (oder aktualisiert wird), außer auf mobilen Geräten.
  • \n
  • mobileStartup - wenn das Trilium-Frontend auf einem mobilen Gerät startet (oder aktualisiert wird).
  • \n
  • backendStartup - wenn das Trilium-Backend startet
  • \n
  • hourly - einmal pro Stunde ausführen. Du kannst das zusätzliche Label runAtHour verwenden, um die genaue Stunde festzulegen.
  • \n
  • daily - einmal pro Tag ausführen
  • \n
", + "run": "Definiert bei welchen Ereignissen das Skript ausgeführt werden soll. Mögliche Werte sind:\n
    \n
  • frontendStartup - wenn das Trilium-Frontend startet (oder aktualisiert wird), außer auf mobilen Geräten.
  • \n
  • mobileStartup - wenn das Trilium-Frontend auf einem mobilen Gerät startet (oder aktualisiert wird).
  • \n
  • backendStartup - wenn das Trilium-Backend startet.
  • \n
  • hourly - einmal pro Stunde ausführen. Du kannst das zusätzliche Label runAtHour verwenden, um die genaue Stunde festzulegen.
  • \n
  • daily - einmal pro Tag ausführen
  • \n
", "run_on_instance": "Definiere, auf welcher Trilium-Instanz dies ausgeführt werden soll. Standardmäßig alle Instanzen.", "run_at_hour": "Zu welcher Stunde soll das laufen? Sollte zusammen mit #runu003dhourly verwendet werden. Kann für mehr Läufe im Laufe des Tages mehrfach definiert werden.", "disable_inclusion": "Skripte mit dieser Bezeichnung werden nicht in die Ausführung des übergeordneten Skripts einbezogen.", @@ -676,7 +684,8 @@ "export_as_image_svg": "SVG (Vektor)", "note_map": "Notizen Karte", "view_revisions": "Notizrevisionen...", - "advanced": "Erweitert" + "advanced": "Erweitert", + "view_ocr_text": "OCR text anzeigen" }, "onclick_button": { "no_click_handler": "Das Schaltflächen-Widget „{{componentId}}“ hat keinen definierten Klick-Handler" @@ -772,7 +781,10 @@ "expand_tooltip": "Erweitert die direkten Unterelemente dieser Sammlung (eine Ebene tiefer). Für weitere Optionen auf den Pfeil rechts klicken.", "expand_first_level": "Direkte Unterelemente erweitern", "expand_nth_level": "{{depth}} Ebenen erweitern", - "hide_child_notes": "Untergeordnete Notizen im Baum ausblenden" + "hide_child_notes": "Untergeordnete Notizen im Baum ausblenden", + "open_all_in_tabs": "Alle öffnen", + "open_all_in_tabs_tooltip": "Alle Resultate in neuen Tabs öffnen", + "open_all_confirm": "Dies öffnet {{count}} Notizen in neuen Tabs. Fortfahren?" }, "edited_notes": { "no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...", @@ -826,7 +838,8 @@ "collapse": "Auf normale Größe reduzieren", "title": "Notizkarte", "fix-nodes": "Knoten fixieren", - "link-distance": "Verbindungslänge" + "link-distance": "Verbindungslänge", + "too-many-notes": "Dieser Unterbaum enthält {{count}} Notizen, welche die Limitierung von {{max}} überschreitet die in der Notizkarte dargestellt werden können." }, "note_paths": { "title": "Notizpfade", @@ -874,7 +887,7 @@ "limit": "Limit", "limit_description": "Begrenze die Anzahl der Ergebnisse", "debug": "debuggen", - "debug_description": "Debug gibt zusätzliche Debuginformationen in die Konsole aus, um das Debuggen komplexer Abfragen zu erleichtern", + "debug_description": "Debug gibt zusätzliche Protokolleinträge in der Konsole aus, um die Fehlerdiagnose komplexer Abfragen zu erleichtern", "action": "Aktion", "search_button": "Suchen", "search_execute": "Aktionen suchen und ausführen", @@ -906,7 +919,7 @@ }, "debug": { "debug": "Debuggen", - "debug_info": "Debug gibt zusätzliche Debuginformationen in die Konsole aus, um das Debuggen komplexer Abfragen zu erleichtern.", + "debug_info": "Debug gibt zusätzliche Protokolleinträge in der Konsole aus, um die Fehlerdiagnose komplexer Abfragen zu erleichtern.", "access_info": "Um auf die Debug-Informationen zuzugreifen, führe die Abfrage aus und klicke oben links auf \"Backend-Log anzeigen\"." }, "fast_search": { @@ -1031,7 +1044,8 @@ "note_already_in_diagram": "Die Notiz \"{{title}}\" ist schon im Diagram.", "enter_title_of_new_note": "Gebe den Titel der neuen Notiz ein", "default_new_note_title": "neue Notiz", - "click_on_canvas_to_place_new_note": "Klicke auf den Canvas, um eine neue Notiz zu platzieren" + "click_on_canvas_to_place_new_note": "Klicke auf den Canvas, um eine neue Notiz zu platzieren", + "rename_relation": "Beziehung umbenennen" }, "backend_log": { "refresh": "Aktualisieren" @@ -1040,15 +1054,17 @@ "title": "Konsistenzprüfungen", "find_and_fix_button": "Finde und behebe die Konsistenzprobleme", "finding_and_fixing_message": "Konsistenzprobleme finden und beheben...", - "issues_fixed_message": "Konsistenzprobleme sollten behoben werden." + "issues_fixed_message": "Konsistenzprobleme sollten behoben werden.", + "find_and_fix_label": "Finde und behebe Konsistenzprobleme", + "find_and_fix_description": "Suche nach Probleme mit der Datenkonsistenz in der Datenbank und repariere diese automatisch." }, "database_anonymization": { "title": "Datenbankanonymisierung", "full_anonymization": "Vollständige Anonymisierung", - "full_anonymization_description": "Durch diese Aktion wird eine neue Kopie der Datenbank erstellt und anonymisiert (der gesamte Notizinhalt wird entfernt und nur die Struktur und einige nicht vertrauliche Metadaten bleiben übrig), sodass sie zu Debugging-Zwecken online geteilt werden kann, ohne befürchten zu müssen, dass Ihre persönlichen Daten verloren gehen.", + "full_anonymization_description": "Erstellt eine Kopie der Datenbank, wo der Inhalt der Notizen entfernt wurde. Es vergleicht lediglich die Struktur sowie nicht-kritische Metadaten. Sicher zum teilen für die Fehlerdiagnose.", "save_fully_anonymized_database": "Speichere eine vollständig anonymisierte Datenbank", "light_anonymization": "Leichte Anonymisierung", - "light_anonymization_description": "Durch diese Aktion wird eine neue Kopie der Datenbank erstellt und eine leichte Anonymisierung vorgenommen – insbesondere wird nur der Inhalt aller Notizen entfernt, Titel und Attribute bleiben jedoch erhalten. Darüber hinaus bleiben benutzerdefinierte JS-Frontend-/Backend-Skriptnotizen und benutzerdefinierte Widgets erhalten. Dies bietet mehr Kontext zum Debuggen der Probleme.", + "light_anonymization_description": "Erstellt eine Kopie wo der Inhalt der Notiz entfernt ist, jedoch Title, Attribute und benutzerdefinierte Skripte erhalten bleiben. Hilfreich zur Fehlerdiagnose.", "choose_anonymization": "Du kannst selbst entscheiden, ob du eine vollständig oder leicht anonymisierte Datenbank bereitstellen möchten. Selbst eine vollständig anonymisierte Datenbank ist sehr nützlich. In einigen Fällen kann jedoch eine leicht anonymisierte Datenbank den Prozess der Fehlererkennung und -behebung beschleunigen.", "save_lightly_anonymized_database": "Speichere eine leicht anonymisierte Datenbank", "existing_anonymized_databases": "Vorhandene anonymisierte Datenbanken", @@ -1057,14 +1073,17 @@ "error_creating_anonymized_database": "Die anonymisierte Datenbank konnte nicht erstellt werden. Überprüfe die Backend-Protokolle auf Details", "successfully_created_fully_anonymized_database": "Vollständig anonymisierte Datenbank in {{anonymizedFilePath}} erstellt", "successfully_created_lightly_anonymized_database": "Leicht anonymisierte Datenbank in {{anonymizedFilePath}} erstellt", - "no_anonymized_database_yet": "Noch keine anonymisierte Datenbank." + "no_anonymized_database_yet": "Noch keine anonymisierte Datenbank.", + "description": "Erstellt eine anonymisierte Kopie deiner Datenbank für den Austausch mit Entwicklern zur Fehlerdiagnose, ohne persönliche Daten preiszugeben." }, "database_integrity_check": { "title": "Datenbankintegritätsprüfung", "check_button": "Überprüfe die Datenbankintegrität", "checking_integrity": "Datenbankintegrität prüfen...", "integrity_check_succeeded": "Integritätsprüfung erfolgreich – keine Probleme gefunden.", - "integrity_check_failed": "Integritätsprüfung fehlgeschlagen: {{results}}" + "integrity_check_failed": "Integritätsprüfung fehlgeschlagen: {{results}}", + "check_integrity_label": "Überprüfe die Datenbankintegrität", + "check_integrity_description": "Überprüfe die Datenbank auf SQLite Ebene auf Fehler." }, "sync": { "title": "Synchronisieren", @@ -1074,14 +1093,18 @@ "filling_entity_changes": "Entitätsänderungszeilen werden gefüllt...", "sync_rows_filled_successfully": "Synchronisierungszeilen erfolgreich gefüllt", "finished-successfully": "Synchronisierung erfolgreich beendet.", - "failed": "Synchronisierung fehlgeschlagen: {{message}}" + "failed": "Synchronisierung fehlgeschlagen: {{message}}", + "force_full_sync_label": "Vollständige Synchronisierung erzwingen", + "force_full_sync_description": "Vollständige Synchronisation mit dem Sync-Server auslösen, lädt alle Änderungen erneut hoch." }, "vacuum_database": { "title": "Datenbank aufräumen", "description": "Dadurch wird die Datenbank neu erstellt, was normalerweise zu einer kleineren Datenbankdatei führt. Es werden keine Daten tatsächlich geändert.", "button_text": "Datenbank aufräumen", "vacuuming_database": "Datenbank wird geleert...", - "database_vacuumed": "Die Datenbank wurde geleert" + "database_vacuumed": "Die Datenbank wurde geleert", + "vacuum_label": "Datenbank bereinigen", + "vacuum_description": "Datenbank neu aufbauen um die Dateigröße zu reduzieren. Keine Daten werden verändert." }, "fonts": { "theme_defined": "Thema definiert", @@ -1092,7 +1115,7 @@ "note_tree_font": "Notizbaum-Schriftart", "note_detail_font": "Notiz-Detail-Schriftart", "monospace_font": "Minivan (Code) Schriftart", - "not_all_fonts_available": "Möglicherweise sind nicht alle aufgelisteten Schriftarten auf Ihrem System verfügbar.", + "not_all_fonts_available": "Möglicherweise sind nicht alle aufgelisteten Schriftarten auf Ihrem System verfügbar", "generic-fonts": "Generische Schriftarten", "sans-serif-system-fonts": "Sans-serif Systemschriftarten", "serif-system-fonts": "Serif Systemschriftarten", @@ -1101,7 +1124,9 @@ "serif": "Serif", "sans-serif": "Sans Serif", "monospace": "Monospace", - "system-default": "System Standard" + "system-default": "System Standard", + "custom_fonts": "Benutzerdefinierte Schriftarten verwenden", + "preview": "Vorschau" }, "max_content_width": { "title": "Inhaltsbreite", @@ -1134,7 +1159,10 @@ "layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert.", "auto_theme": "Alt (Folge dem Farbschema des Systems)", "light_theme": "Alt (Hell)", - "dark_theme": "Alt (Dunkel)" + "dark_theme": "Alt (Dunkel)", + "modern_themes": "Modern", + "legacy_themes": "Veraltet", + "custom_themes": "Benutzerdefiniert" }, "zoom_factor": { "title": "Zoomfaktor (nur Desktop-Build)", @@ -1162,17 +1190,25 @@ }, "images": { "images_section_title": "Bilder", - "download_images_automatically": "Lade Bilder automatisch herunter, um sie offline zu verwenden.", - "download_images_description": "Eingefügter HTML-Code kann Verweise auf Online-Bilder enthalten. Trilium findet diese Verweise und lädt die Bilder herunter, sodass sie offline verfügbar sind.", - "enable_image_compression": "Bildkomprimierung aktivieren", - "max_image_dimensions": "Maximale Breite/Höhe eines Bildes in Pixel (die Größe des Bildes wird geändert, wenn es diese Einstellung überschreitet).", - "jpeg_quality_description": "JPEG-Qualität (10 – schlechteste Qualität, 100 – beste Qualität, 50 – 85 wird empfohlen)", - "max_image_dimensions_unit": "Pixel" + "download_images_automatically": "Bilder automatisch herunterladen", + "download_images_description": "Referenzierte online Bilder vom eingefügten HTML-Code herunterladen, damit diese offline zur Verfügung stehen.", + "enable_image_compression": "Bildkomprimierung", + "max_image_dimensions": "Maximale Maße eines Bildes", + "jpeg_quality_description": "Empfohlener Bereich liegt zwischen 50-85. Niedrige Werte reduzieren die Dateigröße, höhere Werte bewahren Details.", + "max_image_dimensions_unit": "Pixel", + "enable_image_compression_description": "Komprimieren und skalieren Bilder, wenn diese hochgeladen oder eingefügt werden.", + "max_image_dimensions_description": "Bilder, die diese Größe überschreiten, werden automatisch verkleinert.", + "jpeg_quality": "JPEG Qualität", + "ocr_section_title": "Text Extrahierung (OCR)", + "ocr_related_content_languages": "Inhaltssprachen (für Textextrahierung verwendet)", + "ocr_auto_process": "Neue Dateien automatisch verarbeiten", + "ocr_auto_process_description": "Text automatisch extrahieren von neu hochgeladenen oder eingefügten Dateien.", + "ocr_min_confidence": "Minimale Sicherheit" }, "attachment_erasure_timeout": { "attachment_erasure_timeout": "Zeitüberschreitung beim Löschen von Anhängen", - "erase_attachments_after": "Nicht verwendete Anhänge löschen nach:", - "manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):", + "erase_attachments_after": "Nicht verwendete Anhänge löschen nach", + "manual_erasing_description": "Manuelle Löschung aktivieren, ignoriert den obigen Zeitpunkt.", "erase_unused_attachments_now": "Lösche jetzt nicht verwendete Anhangnotizen", "unused_attachments_erased": "Nicht verwendete Anhänge wurden gelöscht." }, @@ -1182,27 +1218,29 @@ }, "note_erasure_timeout": { "note_erasure_timeout_title": "Beachte das Zeitlimit für die Löschung", - "erase_notes_after": "Notizen löschen nach:", - "manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):", + "erase_notes_after": "Notizen löschen nach", + "manual_erasing_description": "Manuelle Löschung aktivieren, ignoriert den obigen Zeitpunkt.", "erase_deleted_notes_now": "Jetzt gelöschte Notizen löschen", "deleted_notes_erased": "Gelöschte Notizen wurden gelöscht." }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Snapshot-Intervall für Notizrevisionen", "note_revisions_snapshot_description": "Das Snapshot-Zeitintervall für Notizrevisionen ist die Zeit, nach der eine neue Notizrevision erstellt wird. Weitere Informationen findest du im Wiki.", - "snapshot_time_interval_label": "Zeitintervall für Notiz-Revisions-Snapshot:" + "snapshot_time_interval_label": "Snapshot Intervall" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "Limit für Notizrevision-Snapshots", "note_revisions_snapshot_limit_description": "Das Limit für Notizrevision-Snapshots bezieht sich auf die maximale Anzahl von Revisionen, die für jede Notiz gespeichert werden können. Dabei bedeutet -1, dass es kein Limit gibt, und 0 bedeutet, dass alle Revisionen gelöscht werden. Du kannst das maximale Limit für Revisionen einer einzelnen Notiz über das Label #versioningLimit festlegen.", - "snapshot_number_limit_label": "Limit der Notizrevision-Snapshots:", + "snapshot_number_limit_label": "Maximale Revisionen", "erase_excess_revision_snapshots": "Überschüssige Revision-Snapshots jetzt löschen", "erase_excess_revision_snapshots_prompt": "Überschüssige Revision-Snapshots wurden gelöscht.", - "snapshot_number_limit_unit": "Momentaufnahmen" + "snapshot_number_limit_unit": "Momentaufnahmen", + "note_revisions_snapshot_limit_description_short": "Maximale Anzahl an Revisionen pro Notiz. Nutze -1 für unlimitiert oder 0 zum deaktivieren.", + "erase_excess_revision_snapshots_description": "Lösche Revisionen aller Notizen die das Limit überschreiten." }, "search_engine": { "title": "Suchmaschine", - "custom_search_engine_info": "Für eine benutzerdefinierte Suchmaschine müssen sowohl ein Name als auch eine URL festgelegt werden. Wenn keine dieser Optionen festgelegt ist, wird DuckDuckGo als Standardsuchmaschine verwendet.", + "custom_search_engine_info": "Wird verwendet bei der Suche im Web für den ausgewählten Text. Wenn nicht konfiguriert, wird DuckDuckGo verwendet.", "predefined_templates_label": "Vordefinierte Suchmaschinenvorlagen", "bing": "Bing", "baidu": "Baidu", @@ -1282,7 +1320,9 @@ "date-and-time": "Datum & Uhrzeit", "path": "Pfad", "database_backed_up_to": "Die Datenbank wurde gesichert unter {{backupFilePath}}", - "no_backup_yet": "noch kein Backup" + "no_backup_yet": "noch kein Backup", + "title": "Sicherung", + "download": "Download" }, "etapi": { "title": "ETAPI", @@ -1321,14 +1361,17 @@ "protected_session_timeout_description": "Das Zeitlimit für geschützte Sitzungen ist ein Zeitraum, nach dem die geschützte Sitzung aus dem Speicher des Browsers gelöscht wird. Dies wird ab der letzten Interaktion mit geschützten Notizen gemessen. Sehen", "wiki": "Wiki", "for_more_info": "für weitere Informationen.", - "protected_session_timeout_label": "Zeitüberschreitung der geschützten Sitzung:", + "protected_session_timeout_label": "Automatisches beenden der Sitzung nach", "reset_confirmation": "Durch das Zurücksetzen des Passworts verlierst du für immer den Zugriff auf alle Ihre bestehenden geschützten Notizen. Möchtest du das Passwort wirklich zurücksetzen?", "reset_success_message": "Das Passwort wurde zurückgesetzt. Bitte lege ein neues Passwort fest", "change_password_heading": "Kennwort ändern", "set_password_heading": "Passwort festlegen", "set_password": "Passwort festlegen", "password_mismatch": "Neue Passwörter sind nicht dasselbe.", - "password_changed_success": "Das Passwort wurde geändert. Trilium wird neu geladen, nachdem du auf OK geklickt hast." + "password_changed_success": "Das Passwort wurde geändert. Trilium wird neu geladen, nachdem du auf OK geklickt hast.", + "change_password_description": "Aktualisiere dein aktuelles Passwort", + "reset_password": "Passwort zurücksetzen", + "cancel": "Abbrechen" }, "shortcuts": { "keyboard_shortcuts": "Tastaturkürzel", @@ -1349,7 +1392,10 @@ "description": "Diese Optionen gelten nur für Desktop-Builds. Browser verwenden ihre eigene native Rechtschreibprüfung.", "enable": "Aktiviere die Rechtschreibprüfung", "language_code_label": "Sprachcode(s)", - "restart-required": "Änderungen an den Rechtschreibprüfungsoptionen werden nach dem Neustart der Anwendung wirksam." + "restart-required": "Änderungen an den Rechtschreibprüfungsoptionen werden nach dem Neustart der Anwendung wirksam.", + "custom_dictionary_title": "Benutzerdefiniertes Wörterbuch", + "custom_dictionary_edit": "Benutzerdefiniert Wörter", + "custom_dictionary_open": "Wörterbuch bearbeiten" }, "sync_2": { "config_title": "Synchronisierungskonfiguration", @@ -1458,7 +1504,8 @@ "task-list": "Aufgabenliste", "new-feature": "Neu", "collections": "Sammlungen", - "spreadsheet": "Tabelle" + "spreadsheet": "Tabelle", + "llm-chat": "KI-Chat" }, "protect_note": { "toggle-on": "Notiz schützen", @@ -1660,7 +1707,8 @@ "theme_none": "Keine Syntaxhervorhebung", "theme_group_light": "Helle Themen", "theme_group_dark": "Dunkle Themen", - "copy_title": "Kopiere in Zwischenablage" + "copy_title": "Kopiere in Zwischenablage", + "click_to_copy": "Klicke zum kopieren" }, "classic_editor_toolbar": { "title": "Format" @@ -1679,7 +1727,7 @@ "title": "Fixiert", "description": "Werkzeuge erscheinen im \"Format\" Tab." }, - "multiline-toolbar": "Toolbar wenn nötig in mehreren Zeilen darstellen." + "multiline-toolbar": "Toolbar wenn nötig in mehreren Zeilen darstellen" } }, "electron_context_menu": { @@ -1770,8 +1818,9 @@ "custom_date_time_format": { "title": "Benutzerdefiniertes Datums-/Zeitformat", "description": "Passe das Format des Datums und der Uhrzeit an, die über oder die Symbolleiste eingefügt werden. Die verfügbaren Format-Tokens sind unter Day.js docs zu finden.", - "format_string": "Format Zeichenfolge:", - "formatted_time": "Formatiertes Datum/Uhrzeit:" + "format_string": "Format Zeichenfolge", + "formatted_time": "Formatiertes Datum/Uhrzeit", + "preview": "Vorschau: {{preview}}" }, "multi_factor_authentication": { "title": "Multi-Faktor-Authentifizierung", @@ -2002,7 +2051,9 @@ "title": "Experimentelle Optionen", "disclaimer": "Diese Optionen sind experimentell und können Instabilitäten verursachen. Achtsam zu verwenden.", "new_layout_name": "Neues Layout", - "new_layout_description": "Probiere das neue Layout für eine modernere Darstellung und verbesserte Benutzbarkeit aus. Kann sich in Zukunft stark ändern." + "new_layout_description": "Probiere das neue Layout für eine modernere Darstellung und verbesserte Benutzbarkeit aus. Kann sich in Zukunft stark ändern.", + "llm_name": "KI- / LLM-Chat", + "llm_description": "Aktiviert die KI-Chat Seitenleiste sowie LLM-Chat Notizen die von einem Sprachmodell betrieben werden." }, "server": { "unknown_http_error_title": "Kommunikationsfehler mit dem Server", @@ -2186,5 +2237,82 @@ "sample_xy": "XY", "sample_venn": "Mengen", "sample_ishikawa": "Ursache-Wirkung" + }, + "revisions": { + "title": "Notizrevisionen" + }, + "database": { + "title": "Datenbank" + }, + "search": { + "title": "Suche", + "fuzzy_matching_label": "Fehlertoleranz bei Suche" + }, + "text_editor": { + "title": "Editor" + }, + "llm_chat": { + "placeholder": "Schreibe eine Nachricht...", + "send": "Senden", + "sending": "Sende...", + "empty_state": "Starte eine Unterhaltung indem du unten eine Nachricht schreibst.", + "searching_web": "Suche im Web...", + "web_search": "Websuche", + "note_tools": "Notizzugriff", + "sources": "Quellen", + "legacy_models": "Veraltete Modelle", + "thinking": "Denke...", + "thought_process": "Denkprozess", + "result": "Ergebnis", + "error": "Fehler", + "tool_error": "Fehlgeschlagen", + "total_tokens": "{{total}} Token", + "tokens": "Token", + "context_used": "{{percentage}}% verwendet", + "no_provider_message": "Kein KI-Anbieter konfiguriert. Fügen einen hinzu, um zu chatten.", + "add_provider": "KI-Anbieter hinzufügen", + "stop": "Stopp" + }, + "sidebar_chat": { + "title": "KI-Chat", + "launcher_title": "KI-Chat öffnen", + "new_chat": "Neuen Chat starten", + "save_chat": "Chat in Notiz sichern", + "empty_state": "Starte Unterhaltung", + "history": "Chathistorie", + "recent_chats": "Kürzliche Chats", + "no_chats": "Keine vorherigen Chats" + }, + "llm": { + "tools": { + "clone_note": "Notiz duplizieren", + "move_note": "Notiz verschieben", + "delete_note": "Notiz löschen", + "rename_note": "Notiz umbenennen", + "web_search": "Websuche", + "create_note": "Notiz erstellen", + "append_to_note": "An Notiz anhängen", + "get_note": "Notiz beziehen", + "search_notes": "Notizen durchsuchen" + }, + "cancel": "Abbrechen", + "api_key": "API Schlüssel", + "delete_provider": "Löschen", + "actions": "Aktionen", + "provider_type": "Anbieter", + "provider_name": "Name", + "settings_title": "KI / LLM" + }, + "mind-map": { + "summary": "Zusammenfassung", + "link": "Link" + }, + "ocr": { + "view_extracted_text": "Extrahierten Text anzeigen (OCR)", + "open_media_settings": "Öffne Einstellungen", + "processing": "Verarbeite...", + "loading_text": "Lädt OCR Text...", + "extracted_text_title": "Extrahierter Text (OCR)", + "extracted_text": "Extrahierter Text (OCR)" } } From 1bf36574e2356f9b48ba458830f90a25ff95af3e Mon Sep 17 00:00:00 2001 From: Artyom Rybakov Date: Tue, 14 Apr 2026 08:02:32 +0200 Subject: [PATCH 164/203] Translated using Weblate (Russian) Currently translated at 91.8% (1726 of 1880 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/ --- .../src/translations/ru/translation.json | 110 ++++++++++++------ 1 file changed, 73 insertions(+), 37 deletions(-) diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json index bcf8b6f506..9065c615da 100644 --- a/apps/client/src/translations/ru/translation.json +++ b/apps/client/src/translations/ru/translation.json @@ -77,18 +77,28 @@ }, "delete_notes": { "close": "Закрыть", - "erase_notes_description": "Обычное (мягкое) удаление только отмечает заметки как удалённые, и их можно восстановить (в диалоговом окне последних изменений) в течение определённого времени. Если выбрать этот параметр, заметки будут удалены немедленно, и восстановить их будет невозможно.", + "erase_notes_description": "Стереть заметки немедленно вместо мягкого удаления. Это действие необратимо и вызовет перезагрузку приложения.", "delete_all_clones_description": "Удалить также все клоны (можно отменить в последних изменениях)", "erase_notes_warning": "Удалить заметки без возможности восстановления, включая все клоны. Это приведёт к принудительной перезагрузке приложения.", - "notes_to_be_deleted": "Следующие заметки будут удалены ({{notesCount}})", + "notes_to_be_deleted": "Заметки, подлежащие удалению ({{notesCount}})", "no_note_to_delete": "Заметка не будет удалена (только клоны).", - "broken_relations_to_be_deleted": "Следующие отношения будут разорваны и удалены ({{relationCount}})", - "cancel": "Отмена" + "broken_relations_to_be_deleted": "Разорванные отношения ({{relationCount}})", + "cancel": "Отмена", + "title": "Удалить заметки", + "clones_label": "Клоны", + "delete_clones_description_one": "Также удалить {{count}} другой клон. Можно отменить в последних изменениях.", + "delete_clones_description_few": "Также удалить {{count}} других клона. Можно отменить в последних изменениях.", + "delete_clones_description_many": "Также удалить {{count}} других клонов. Можно отменить в последних изменениях.", + "erase_notes_label": "Стереть навсегда", + "table_note_with_relation": "Заметка с отношением", + "table_relation": "Отношение", + "table_points_to": "Указывает на (удалено)", + "delete": "Удалить" }, "database_anonymization": { - "light_anonymization_description": "Это действие создаст новую копию базы данных и выполнит её лёгкую анонимизацию — в частности, будет удалён только контент всех заметок, но заголовки и атрибуты останутся. Кроме того, будут сохранены пользовательские заметки, содержащие JavaScript-скрипты frontend/backend и пользовательские виджеты. Это даёт больше контекста для отладки проблем.", + "light_anonymization_description": "Создает копию с удалением содержимого заметок, но заголовками, атрибутами и пользовательскими скриптами/виджетами. Даёт больше контекста для отладки.", "choose_anonymization": "Вы можете самостоятельно решить, хотите ли вы предоставить полностью или частично анонимизированную базу данных. Даже полностью анонимизированная база данных очень полезна, однако в некоторых случаях частично анонимизированная база данных может ускорить процесс выявления и исправления ошибок.", - "full_anonymization_description": "Это действие создаст новую копию базы данных и анонимизирует ее (удалит все содержимое заметок и оставит только структуру и некоторые неконфиденциальные метаданные) для совместного использования в Интернете в целях отладки без опасения утечки ваших личных данных.", + "full_anonymization_description": "Создает копию базы данных с удалением всего содержимого заметок, оставляя только структуру и неконфиденциальные метаданные. Безопасно для обмена в сети при отладке проблем.", "title": "Анонимизация базы данных", "full_anonymization": "Полная анонимизация", "light_anonymization": "Легкая анонимизация", @@ -136,20 +146,23 @@ "title": "Языки контента" }, "theme": { - "theme_label": "Тема", + "theme_label": "Тема приложения", "override_theme_fonts_label": "Переопределить шрифты темы", - "triliumnext": "Trilium (следует системной цветовой схеме)", - "triliumnext-light": "Trilium (Светлая)", - "triliumnext-dark": "Trilium (Темная)", - "title": "Тема приложения", + "triliumnext": "Использовать цветовую схему системы", + "triliumnext-light": "Светлая", + "triliumnext-dark": "Тёмная", + "title": "Пользовательский интерфейс", "layout": "Макет", "layout-vertical-title": "Вертикальный", "layout-horizontal-title": "Горизонтальный", - "auto_theme": "Legacy (следует системной цветовой схеме)", - "light_theme": "Legacy (светлая)", - "dark_theme": "Legacy (темная)", + "auto_theme": "Использовать цветовую схему системы", + "light_theme": "Светлая", + "dark_theme": "Тёмная", "layout-horizontal-description": "панель запуска находится под панелью вкладок, панель вкладок теперь занимает всю ширину.", - "layout-vertical-description": "панель запуска находится слева (по умолчанию)" + "layout-vertical-description": "панель запуска находится слева (по умолчанию)", + "modern_themes": "Современные", + "legacy_themes": "Устаревшие", + "custom_themes": "Пользовательские" }, "tasks": { "due": { @@ -1038,7 +1051,10 @@ "expand_nth_level": "Развернуть уровни: {{depth}} шт.", "expand_first_level": "Развернуть прямые дочерние уровни", "expand_tooltip": "Разщвернуть дочерние элементы этой коллекции (на один уровень вложенности). Для получения дополнительных параметров нажмите стрелку справа.", - "hide_child_notes": "Скрыть дочерние заметки в дереве" + "hide_child_notes": "Скрыть дочерние заметки в дереве", + "open_all_in_tabs": "Открыть все", + "open_all_in_tabs_tooltip": "Открыть все результаты в новых вкладках", + "open_all_confirm": "Это откроет заметки ({{count}}) в новых вкладках. Продолжить?" }, "edited_notes": { "deleted": "(удалено)", @@ -1209,20 +1225,24 @@ "size": "Размер", "serif": "С засечками", "monospace": "Моноширинный", - "main_font": "Основной шрифт", + "main_font": "Текст интерфейса", "font_family": "Семейство шрифтов", "sans-serif": "Без засечек", "system-default": "Системный по умолчанию", "theme_defined": "Определяется темой", "generic-fonts": "Стандартные шрифты", - "note_tree_font": "Шрифт дерева заметок", - "note_detail_font": "Шрифт заметки", - "monospace_font": "Моноширинный шрифт (для кода)", + "note_tree_font": "Текст дерева заметок", + "note_detail_font": "Текст документа", + "monospace_font": "Моноширинный текст", "sans-serif-system-fonts": "Системные шрифты без засечек", "serif-system-fonts": "Системные шрифты с засечками", "monospace-system-fonts": "Моноширинные системные шрифты", "handwriting-system-fonts": "Шрифты системы рукописного ввода", - "not_all_fonts_available": "Не все перечисленные шрифты могут быть доступны в вашей системе." + "not_all_fonts_available": "Не все перечисленные шрифты могут быть доступны в вашей системе", + "preview": "Предпросмотр", + "monospace_font_description": "Используется для заметок кода и блоков кода", + "size_relative_to_general": "Размер относительно общего размера шрифта", + "apply_changes": "Перезагрузить, чтобы применить изменения" }, "max_content_width": { "max_width_unit": "пикселей", @@ -1242,11 +1262,14 @@ "images": { "images_section_title": "Изображения", "max_image_dimensions_unit": "пикселей", - "download_images_automatically": "Автоматическая загрузка изображений для использования в автономном режиме.", - "download_images_description": "Вставленный HTML-код может содержать ссылки на онлайн-изображения. Trilium найдет эти ссылки и загрузит изображения, чтобы они были доступны офлайн.", - "enable_image_compression": "Включить сжатие изображений", - "max_image_dimensions": "Максимальная ширина/высота изображения (размер изображения будет изменен, если он превысит этот параметр).", - "jpeg_quality_description": "Качество JPEG (10 — худшее качество, 100 — наилучшее качество, рекомендуется 50 — 85)" + "download_images_automatically": "Автоматическая загрузка изображений", + "download_images_description": "Скачивать изображения указанные в вставленном HTML-коде, чтобы они были доступны офлайн.", + "enable_image_compression": "Сжатие изображений", + "max_image_dimensions": "Максимальные размеры изображения", + "jpeg_quality_description": "Рекомендуемый диапазон - 50–85. Более низкие значения уменьшают размер файла, более высокие - сохраняют детализацию.", + "enable_image_compression_description": "Сжимать и изменять размер изображений при их загрузке или вставке.", + "max_image_dimensions_description": "Размер изображений, превышающих этот размер, будет изменен автоматически.", + "jpeg_quality": "Качество JPEG" }, "search_engine": { "bing": "Bing", @@ -1540,7 +1563,7 @@ "unit": "символов", "title": "Автоматическое переключение в режим \"только для чтения\"", "description": "Максимальный размер заметки до переключения в режим только для чтения — это размер, после которого заметки будут отображаться в режиме «только для чтения» (из соображений производительности).", - "label": "Максимальный размер заметки до переключения в режим только для чтения (заметки с кодом)" + "label": "Максимальный размер заметки до переключения в режим только для чтения" }, "inherited_attribute_list": { "title": "Унаследованные атрибуты", @@ -1598,14 +1621,16 @@ "note_already_in_diagram": "Заметка \"{{title}}\" уже есть на диаграмме.", "connection_exists": "Связь '{{name}}' между этими заметками уже существует.", "specify_new_relation_name": "Укажите новое имя связи (допустимые символы: буквы, цифры, двоеточие и подчеркивание):", - "start_dragging_relations": "Начните перетягивать отношения отсюда на другую заметку." + "start_dragging_relations": "Начните перетягивать отношения отсюда на другую заметку.", + "rename_relation": "Переименовать отношение" }, "vacuum_database": { "title": "Уменьшение размера файла базы данных", "description": "Это приведет к перестройке базы данных, что, скорее всего, уменьшит размер её файла. Данные не будут изменены.", "button_text": "Уменьшить размер файла базы данных", "vacuuming_database": "Уменьшение размера файла базы данных...", - "database_vacuumed": "База данных была перестроена" + "database_vacuumed": "База данных была перестроена", + "vacuum_label": "Уменьшить размер файла базы данных" }, "vim_key_bindings": { "use_vim_keybindings_in_code_notes": "Сочетания клавиш Vim", @@ -1726,13 +1751,15 @@ "check_button": "Проверить целостность базы данных", "checking_integrity": "Проверка целостности базы данных...", "integrity_check_succeeded": "Проверка целостности прошла успешно - проблем не обнаружено.", - "integrity_check_failed": "Проверка целостности завершена с ошибками: {{results}}" + "integrity_check_failed": "Проверка целостности завершена с ошибками: {{results}}", + "check_integrity_label": "Проверить целостность базы данных" }, "consistency_checks": { "find_and_fix_button": "Найти и устранить проблемы целостности", "finding_and_fixing_message": "Поиск и устранение проблем целостности...", "title": "Проверки целостности", - "issues_fixed_message": "Все обнаруженные проблемы с согласованностью теперь устранены." + "issues_fixed_message": "Все обнаруженные проблемы с согласованностью теперь устранены.", + "find_and_fix_label": "Найти и устранить проблемы целостности" }, "call_to_action": { "next_theme_message": "В настоящее время вы используете старую тему оформления. Хотите попробовать новую тему?", @@ -1969,11 +1996,11 @@ }, "ui-performance": { "title": "Производительность", - "enable-motion": "Включить визуальные эффекты и анимации", - "enable-shadows": "Включить тени", - "enable-backdrop-effects": "Включить эффекты размытия фона меню, всплывающих окон и панелей", - "enable-smooth-scroll": "Включить плавную прокрутку", - "app-restart-required": "(для вступления изменений в силу требуется перезапуск приложения)" + "enable-motion": "Визуальные эффекты и анимации", + "enable-shadows": "Тени", + "enable-backdrop-effects": "Эффекты размытия фона меню, всплывающих окон и панелей", + "enable-smooth-scroll": "Плавная прокрутку", + "app-restart-required": "Требуется перезапуск приложения" }, "collections": { "rendering_error": "Невозможно отобразить содержимое из-за ошибки." @@ -2274,6 +2301,15 @@ "delete_provider_confirmation": "Вы уверены, что желаете удалить провайдера \"{{name}}\"?", "api_key": "Ключ API", "api_key_placeholder": "Введите ваш ключ API", - "cancel": "Отмена" + "cancel": "Отмена", + "tools": { + "clone_note": "Клонировать заметку", + "move_note": "Переместить заметку", + "delete_note": "Удалить заметку", + "rename_note": "Переименовать заметку" + } + }, + "database": { + "title": "База данных" } } From c0c0cea376bb8032184c06f8a3532410d6fd117c Mon Sep 17 00:00:00 2001 From: Artyom Rybakov Date: Tue, 14 Apr 2026 07:57:24 +0200 Subject: [PATCH 165/203] Translated using Weblate (Russian) Currently translated at 100.0% (403 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/ --- .../src/assets/translations/ru/server.json | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/server/src/assets/translations/ru/server.json b/apps/server/src/assets/translations/ru/server.json index cf1b01bcbb..170b0a34a0 100644 --- a/apps/server/src/assets/translations/ru/server.json +++ b/apps/server/src/assets/translations/ru/server.json @@ -114,7 +114,7 @@ "options-title": "Опции", "appearance-title": "Оформление", "shortcuts-title": "Сочетания клавиш", - "images-title": "Изображения", + "images-title": "Медиа", "spellcheck-title": "Проверка орфографии", "password-title": "Пароль", "etapi-title": "ETAPI", @@ -160,7 +160,8 @@ "tab-switcher-title": "Переключатель вкладок", "llm-chat-history-title": "История чата с ИИ", "llm-title": "ИИ / LLM", - "sidebar-chat-title": "Чат с ИИ" + "sidebar-chat-title": "Чат с ИИ", + "custom-dictionary-title": "Пользовательский словарь" }, "tray": { "bookmarks": "Закладки", @@ -361,7 +362,10 @@ "site-theme": "Тема оформления сайта", "image_alt": "Изображение статьи", "on-this-page": "На текущей странице", - "last-updated": "Последнее обновление: {{- date}}" + "last-updated": "Последнее обновление: {{- date}}", + "logo-alt": "Логотип", + "toggle-toc": "Переключение оглавления", + "toggle-navigation": "Переключение навигации" }, "hidden_subtree_templates": { "description": "Описание", @@ -441,5 +445,21 @@ }, "desktop": { "instance_already_running": "Приложение уже запущено, фокус переключен на него." + }, + "password": { + "incorrect": "Вы ввели неверный пароль." + }, + "script": { + "wrong-environment": "Не удается выполнить заметку \"{{- noteTitle}}\" ({{- noteId}}). Это скрипт {{- actualEnv}}, но попытка выполнения была предпринята в {{- expectedEnv}}." + }, + "search": { + "error": { + "in-context": "Ошибка в {{- context}}: {{- message}}", + "reserved-keyword": "\"{{- token}}\" - зарезервированное ключевое слово. Для поиска буквального значения используйте кавычки: \"{{- token}}\"", + "cannot-compare-with": "невозможно сравнить с \"{{- token}}\". Для поиска буквального значения используйте кавычки: \"{{- token}}\"", + "misplaced-expression": "Неподходящее или неполное выражение \"{{- token}}\"", + "fulltext-after-expression": "\"{{- token}}\" - неверное выражение. Для поиска текста поместите его перед фильтрами атрибутов (например, \"{{- token}} #метка\" вместо \"#метка {{- token}}\").", + "unrecognized-expression": "Нераспознанное выражение \"{{- token}}\"" + } } } From 263479adeed24d9ba533d5e2291fa3038eaed55b Mon Sep 17 00:00:00 2001 From: green Date: Tue, 14 Apr 2026 07:30:33 +0200 Subject: [PATCH 166/203] Translated using Weblate (Japanese) Currently translated at 100.0% (403 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ja/ --- apps/server/src/assets/translations/ja/server.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/server/src/assets/translations/ja/server.json b/apps/server/src/assets/translations/ja/server.json index c1f65d4cd4..2197272282 100644 --- a/apps/server/src/assets/translations/ja/server.json +++ b/apps/server/src/assets/translations/ja/server.json @@ -458,5 +458,8 @@ }, "script": { "wrong-environment": "ノート \"{{- noteTitle}}\" ({{- noteId}}) を実行できません。これは {{- actualEnv}} スクリプトですが、{{- expectedEnv}} で実行しようとしました。" + }, + "password": { + "incorrect": "入力されたパスワードが間違っています。" } } From a4c419de6f637377bc944f4ae4cd7e5688186f6e Mon Sep 17 00:00:00 2001 From: Ulices Date: Tue, 14 Apr 2026 23:25:59 +0200 Subject: [PATCH 167/203] Translated using Weblate (Spanish) Currently translated at 87.0% (1637 of 1880 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/ --- .../src/translations/es/translation.json | 72 +++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/apps/client/src/translations/es/translation.json b/apps/client/src/translations/es/translation.json index 990b541636..f3588aac03 100644 --- a/apps/client/src/translations/es/translation.json +++ b/apps/client/src/translations/es/translation.json @@ -90,12 +90,22 @@ "delete_notes": { "close": "Cerrar", "delete_all_clones_description": "Eliminar también todos los clones (se puede deshacer en cambios recientes)", - "erase_notes_description": "La eliminación normal (suave) solo marca las notas como eliminadas y se pueden recuperar (en el cuadro de diálogo de cambios recientes) dentro de un periodo de tiempo. Al marcar esta opción se borrarán las notas inmediatamente y no será posible recuperarlas.", + "erase_notes_description": "Borrar notas inmediatamente en lugar de la eliminación con respaldo. Esto no se puede deshacer y forzará la recarga de la aplicación.", "erase_notes_warning": "Eliminar notas permanentemente (no se puede deshacer), incluidos todos los clones. Esto forzará la recarga de la aplicación.", - "notes_to_be_deleted": "Las siguientes notas serán eliminadas ({{notesCount}})", + "notes_to_be_deleted": "Notas a ser eliminadas ({{notesCount}})", "no_note_to_delete": "No se eliminará ninguna nota (solo clones).", - "broken_relations_to_be_deleted": "Las siguientes relaciones se romperán y serán eliminadas ({{ relationCount}})", - "cancel": "Cancelar" + "broken_relations_to_be_deleted": "Relaciones rotas ({{ relationCount}})", + "cancel": "Cancelar", + "title": "Eliminar notas", + "clones_label": "Clones", + "delete_clones_description_one": "También eliminar otro clon. Puede deshacerse en los cambios recientes.", + "delete_clones_description_many": "También eliminar otros {{count}} clones. Puede deshacerse en los cambios recientes.", + "delete_clones_description_other": "También eliminar otros {{count}} clones. Puede deshacerse en los cambios recientes.", + "erase_notes_label": "Borrar permanentemente", + "table_note_with_relation": "Nota con relación", + "table_relation": "Relación", + "table_points_to": "Apunta a (eliminado)", + "delete": "Eliminar" }, "export": { "export_note_title": "Exportar nota", @@ -206,7 +216,8 @@ "box_size_small": "pequeño (~ 10 líneas)", "box_size_medium": "medio (~ 30 líneas)", "box_size_full": "completo (el cuadro muestra el texto completo)", - "button_include": "Incluir nota" + "button_include": "Incluir nota", + "box_size_expandable": "expandible (colapsado por defecto)" }, "info": { "modalTitle": "Mensaje informativo", @@ -338,7 +349,7 @@ "calendar_root": "marca la nota que debe usarse como raíz para las notas del día. Sólo uno debe estar marcado como tal.", "archived": "las notas con esta etiqueta no serán visibles de forma predeterminada en los resultados de búsqueda (tampoco en los cuadros de diálogo Saltar a, Agregar vínculo, etc.).", "exclude_from_export": "las notas (con su subárbol) no se incluirán en ninguna exportación de notas", - "run": "define en qué eventos debe ejecutarse el script. Los valores posibles son:\n
    \n
  • frontendStartup - cuando Trilium frontend se inicia (o se actualiza), pero no en móvil.
  • \n
  • mobileStartup - cuando Trilium frontend se inicia (o se actualiza), en móvil.
  • \n
  • backendStartup - cuando Trilium backend se inicia
  • \n
  • hourly - se ejecuta una vez por hora. Se puede usar la etiqueta adicional runAtHour para especificar a qué hora.
  • \n
  • daily - se ejecuta una vez al día
  • \n
", + "run": "define en qué eventos debe ejecutarse el script. Los valores posibles son:\n
    \n
  • frontendStartup - cuando el frontend de Trilium se inicia (o se actualiza), pero no en móvil.
  • \n
  • mobileStartup - cuando el frontend de Trilium se inicia (o se actualiza), en móvil.
  • \n
  • backendStartup - cuando el backend de Trilium se inicia
  • \n
  • hourly - se ejecuta una vez por hora. Se puede usar la etiqueta adicional runAtHour para especificar a qué hora.
  • \n
  • daily - se ejecuta una vez al día
  • \n
", "run_on_instance": "Definir en qué instancia de Trilium se debe ejecutar esto. Predeterminado para todas las instancias.", "run_at_hour": "¿A qué hora debería funcionar? Debe usarse junto con #run=hourly. Se puede definir varias veces para varias ejecuciones durante el día.", "disable_inclusion": "los scripts con esta etiqueta no se incluirán en la ejecución del script principal.", @@ -416,7 +427,8 @@ "and_more": "... y {{count}} más.", "print_landscape": "Al exportar a PDF, cambia la orientación de la página a paisaje en lugar de retrato.", "print_page_size": "Al exportar a PDF, cambia el tamaño de la página. Valores soportados: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", - "color_type": "Color" + "color_type": "Color", + "textarea": "Texto multilínea" }, "attribute_editor": { "help_text_body1": "Para agregar una etiqueta, simplemente escriba, por ejemplo. #rock o si desea agregar también valor, p.e. #año = 2020", @@ -678,7 +690,8 @@ "export_as_image": "Exportar como imagen", "export_as_image_png": "PNG (ráster)", "export_as_image_svg": "SVG (vectorial)", - "note_map": "Mapa de la nota" + "note_map": "Mapa de la nota", + "view_ocr_text": "Ver texto OCR" }, "onclick_button": { "no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido" @@ -776,7 +789,10 @@ "expand_first_level": "Expandir hijos inmediatos", "expand_nth_level": "Expandir {{depth}} niveles", "expand_all_levels": "Expandir todos los niveles", - "hide_child_notes": "Ocultar subnotas en el árbol" + "hide_child_notes": "Ocultar subnotas en el árbol", + "open_all_in_tabs": "Abrir todo", + "open_all_in_tabs_tooltip": "Abrir todos los resultados en nuevas pestañas", + "open_all_confirm": "Esto abrirá {{count}} notas en nuevas pestañas. ¿Continuar?" }, "edited_notes": { "no_edited_notes_found": "Aún no hay notas editadas en este día...", @@ -830,7 +846,8 @@ "collapse": "Contraer al tamaño normal", "title": "Mapa de notas", "fix-nodes": "Fijar nodos", - "link-distance": "Distancia de enlace" + "link-distance": "Distancia de enlace", + "too-many-notes": "Este subárbol contiene {{count}} notas, lo que excede el límite de {{max}} que se puede mostrar en el mapa de notas." }, "note_paths": { "title": "Rutas de nota", @@ -1035,7 +1052,8 @@ "note_already_in_diagram": "La nota \"{{title}}\" ya está en el diagrama.", "enter_title_of_new_note": "Ingrese el título de la nueva nota", "default_new_note_title": "nueva nota", - "click_on_canvas_to_place_new_note": "Haga clic en el lienzo para colocar una nueva nota" + "click_on_canvas_to_place_new_note": "Haga clic en el lienzo para colocar una nueva nota", + "rename_relation": "Renombrar relación" }, "backend_log": { "refresh": "Refrescar" @@ -1044,15 +1062,17 @@ "title": "Comprobación de coherencia", "find_and_fix_button": "Buscar y solucionar problemas de coherencia", "finding_and_fixing_message": "Buscando y solucionando problemas de coherencia...", - "issues_fixed_message": "Los problemas de coherencia han sido solucionados." + "issues_fixed_message": "Los problemas de coherencia han sido solucionados.", + "find_and_fix_label": "Buscar y solucionar problemas de coherencia", + "find_and_fix_description": "Escanear y reparar automáticamente cualquier problema de consistencia de datos en la base de datos." }, "database_anonymization": { "title": "Anonimización de bases de datos", "full_anonymization": "Anonimización total", - "full_anonymization_description": "Esta acción creará una nueva copia de la base de datos y la anonimizará (eliminará todo el contenido de las notas y dejará solo la estructura y algunos metadatos no confidenciales) para compartirla en línea con fines de depuración sin temor a filtrar sus datos personales.", + "full_anonymization_description": "Crea una copia de la base de datos con todo el contenido eliminado, dejando solo la estructura y algunos metadatos no confidenciales. Seguro para compartirla en línea con fines de depuración de problemas.", "save_fully_anonymized_database": "Guarde la base de datos completamente anónima", "light_anonymization": "Anonimización ligera", - "light_anonymization_description": "Esta acción creará una nueva copia de la base de datos y realizará una ligera anonimización en ella; específicamente, solo se eliminará el contenido de todas las notas, pero los títulos y atributos permanecerán. Además, se mantendrán las notas de script JS frontend/backend personalizadas y los widgets personalizados. Esto proporciona más contexto para depurar los problemas.", + "light_anonymization_description": "Crea una copia con el contenido de las notas eliminado, excepto los títulos, atributos y scripts/widgets personalizados que permanecerán. Esto proporciona más contexto para el depurado.", "choose_anonymization": "Puede decidir usted mismo si desea proporcionar una base de datos total o ligeramente anónima. Incluso una base de datos totalmente anónima es muy útil; sin embargo, en algunos casos, una base de datos ligeramente anónima puede acelerar el proceso de identificación y corrección de errores.", "save_lightly_anonymized_database": "Guarde una base de datos ligeramente anónima", "existing_anonymized_databases": "Bases de datos anónimas existentes", @@ -1061,14 +1081,17 @@ "error_creating_anonymized_database": "No se pudo crear una base de datos anónima; consulte los registros de backend para obtener más detalles", "successfully_created_fully_anonymized_database": "Se creó una base de datos completamente anónima en {{anonymizedFilePath}}", "successfully_created_lightly_anonymized_database": "Se creó una base de datos ligeramente anónima en {{anonymizedFilePath}}", - "no_anonymized_database_yet": "Aún no hay base de datos anónima." + "no_anonymized_database_yet": "Aún no hay base de datos anónima.", + "description": "Crear una copia anónima de su base de datos para compartir con los desarrolladores cuando depuren problemas, sin exponer datos personales." }, "database_integrity_check": { "title": "Verificación de integridad de la base de datos", "check_button": "Verificar la integridad de la base de datos", "checking_integrity": "Comprobando la integridad de la base de datos...", "integrity_check_succeeded": "La verificación de integridad fue exitosa; no se encontraron problemas.", - "integrity_check_failed": "La verificación de integridad falló: {{results}}" + "integrity_check_failed": "La verificación de integridad falló: {{results}}", + "check_integrity_label": "Verificar la integridad de la base de datos", + "check_integrity_description": "Verifcar que la base de datos no está dañada en el nivel SQLite." }, "sync": { "title": "Sincronizar", @@ -1078,7 +1101,10 @@ "filling_entity_changes": "Rellenar filas de cambios de entidad...", "sync_rows_filled_successfully": "Sincronizar filas completadas correctamente", "finished-successfully": "La sincronización finalizó exitosamente.", - "failed": "La sincronización falló: {{message}}" + "failed": "La sincronización falló: {{message}}", + "force_full_sync_label": "Forzar sincronización completa", + "force_full_sync_description": "Activa una sincronización completa con el servidor de sincronización, resubiendo todos los cambios.", + "fill_entity_changes_label": "Llenar cambios de entidad" }, "vacuum_database": { "title": "Limpiar base de datos", @@ -1169,12 +1195,15 @@ }, "images": { "images_section_title": "Imágenes", - "download_images_automatically": "Descargar imágenes automáticamente para usarlas sin conexión.", + "download_images_automatically": "Descargar imágenes automáticamente", "download_images_description": "El HTML pegado puede contener referencias a imágenes en línea; Trilium encontrará esas referencias y descargará las imágenes para que estén disponibles sin conexión.", "enable_image_compression": "Habilitar la compresión de imágenes", - "max_image_dimensions": "Ancho/alto máximo de una imagen en píxeles (la imagen cambiará de tamaño si excede esta configuración).", + "max_image_dimensions": "Dimensiones máximas de imagen", "max_image_dimensions_unit": "píxeles", - "jpeg_quality_description": "Calidad JPEG (10 - peor calidad, 100 - mejor calidad, se recomienda 50 - 85)" + "jpeg_quality_description": "Se recomienda un rango 50-85. Valores más pequeños reducen el tamaño del archivo, valores más altos preservan los detalles.", + "enable_image_compression_description": "Comprimir y ajustar el tamaño de las imágenes cuando son subidas o pegadas.", + "max_image_dimensions_description": "Las imágenes que excedan el límite de tamaño serán ajustadas automáticamente.", + "jpeg_quality": "Calidad JPEG" }, "attachment_erasure_timeout": { "attachment_erasure_timeout": "Tiempo de espera para borrar archivos adjuntos", @@ -2201,5 +2230,8 @@ "sample_venn": "Venn", "sample_ishikawa": "Ishikawa", "sample_treemap": "Mapa de árbol" + }, + "revisions": { + "title": "Revisiones de nota" } } From 7e1090c59d65fd4d59a1bba6aa7cec6479e77f7c Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 14 Apr 2026 22:40:57 +0200 Subject: [PATCH 168/203] Translated using Weblate (German) Currently translated at 100.0% (403 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/ --- .../src/assets/translations/de/server.json | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/apps/server/src/assets/translations/de/server.json b/apps/server/src/assets/translations/de/server.json index 8dc060b456..124ed7b670 100644 --- a/apps/server/src/assets/translations/de/server.json +++ b/apps/server/src/assets/translations/de/server.json @@ -198,7 +198,8 @@ "december": "Dezember" }, "special_notes": { - "search_prefix": "Suche:" + "search_prefix": "Suche:", + "llm_chat_prefix": "Chat:" }, "test_sync": { "not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.", @@ -226,7 +227,7 @@ "go-to-previous-note-title": "Zur vorherigen Notiz gehen", "go-to-next-note-title": "Zur nächsten Notiz gehen", "new-note-title": "Neue Notiz", - "search-notes-title": "Notizen durchsuchen", + "search-notes-title": "Suche Notiz", "calendar-title": "Kalender", "recent-changes-title": "neue Änderungen", "bookmarks-title": "Lesezeichen", @@ -240,7 +241,7 @@ "shortcuts-title": "Tastenkürzel", "text-notes": "Text Notizen", "code-notes-title": "Code Notizen", - "images-title": "Bilder", + "images-title": "Medien", "spellcheck-title": "Rechtschreibprüfung", "password-title": "Passwort", "etapi-title": "ETAPI", @@ -256,7 +257,11 @@ "inbox-title": "Posteingang", "zen-mode": "Zen-Modus", "command-palette": "Befehlspalette öffnen", - "tab-switcher-title": "Tabauswahl" + "tab-switcher-title": "Tabauswahl", + "llm-chat-history-title": "KI-Chat Historie", + "custom-dictionary-title": "Benutzerdefiniertes Verzeichnis", + "llm-title": "KI / LLM", + "sidebar-chat-title": "KI-Chat" }, "notes": { "new-note": "Neue Notiz", @@ -429,7 +434,10 @@ "last-updated": "Zuletzt aktualisiert am {{- date}}", "subpages": "Unterseiten:", "on-this-page": "Auf dieser Seite", - "expand": "Erweitern" + "expand": "Erweitern", + "toggle-navigation": "Navigation umschalten", + "toggle-toc": "Inhaltsverzeichnis umschalten", + "logo-alt": "Logo" }, "sql_init": { "db_not_initialized_desktop": "Datenbank nicht initialisiert, bitte folge den Anweisungen auf dem Bildschirm.", @@ -437,5 +445,21 @@ }, "desktop": { "instance_already_running": "Es läuft bereits eine Instanz, bitte beachte stattdessen diese Instanz." + }, + "password": { + "incorrect": "Das eingegebene Passwort ist nicht korrekt." + }, + "script": { + "wrong-environment": "Kann die Notiz \"{{- noteTitle}}\" ({{- noteId}}) nicht ausführen. Dies ist ein {{- actualEnv}} Skript, aber die Ausführung wurde im {{- expectedEnv}} versucht." + }, + "search": { + "error": { + "in-context": "Fehler in {{- context}}: {{- message}}", + "reserved-keyword": "\"{{- token}}\" ist ein reserviertes Stichwort. Um eine Wortgenaue Suche durchzuführen, verwende Anführungszeichen \"{{- token}}\"", + "cannot-compare-with": "Vergleich mit \"{{- token}}\" nicht möglich. Um eine Wortgenaue Suche durchzuführen, verwende Anführungszeichen \"{{- token}}\"", + "misplaced-expression": "Fehlplatzierter oder unvollständiger Ausdruck \"{{- token}}\"", + "fulltext-after-expression": "\"{{- token}}\" ist kein valider Ausdruck. Um einen Text zu suchen, platziere ihn vor den Attributfiltern (z.B. \"{{- token}} #label\" anstatt \"#label {{- token}}\").", + "unrecognized-expression": "Nicht erkannter Ausdruck \"{{- token}}\"" + } } } From 2bf2d977ad61d2ecb398e7ad7dbed7bde27a8913 Mon Sep 17 00:00:00 2001 From: green Date: Tue, 14 Apr 2026 08:26:52 +0200 Subject: [PATCH 169/203] Translated using Weblate (Japanese) Currently translated at 99.9% (1879 of 1880 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- .../src/translations/ja/translation.json | 219 +++++++++++------- 1 file changed, 140 insertions(+), 79 deletions(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 3c7d396362..c310f1e846 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -183,10 +183,10 @@ "monday": "月曜日", "first-week-of-the-year": "年の最初の週", "first-week-contains-first-day": "最初の週には、元日が含まれる", - "first-week-contains-first-thursday": "最初の週には、その年の最初の木曜日が含まれる", + "first-week-contains-first-thursday": "最初の週には、最初の木曜日が含まれる(ISO 8601)", "first-week-has-minimum-days": "最初の週は最低日数", "min-days-in-first-week": "最初の週の最低日数", - "first-week-warning": "最初の週のオプションを変更すると、既存のウィークノートと重複する可能性があり、既存のウィークノートはそれに応じて更新されません。", + "first-week-warning": "この設定を変更すると、既存のウィークノートと重複が発生する可能性があります。", "formatting-locale": "日付と数値のフォーマット", "formatting-locale-auto": "アプリケーションの言語に基づいて", "tuesday": "火曜日", @@ -822,17 +822,21 @@ "filling_entity_changes": "エンティティ変更行を入力中...", "sync_rows_filled_successfully": "同期行が正常に入力されました", "finished-successfully": "同期が正常に完了しました。", - "failed": "同期に失敗しました: {{message}}" + "failed": "同期に失敗しました: {{message}}", + "force_full_sync_label": "強制的な完全同期", + "force_full_sync_description": "同期サーバーとの完全同期を実行し、すべての変更を再アップロードします。", + "fill_entity_changes_label": "エンティティの変更を反映", + "fill_entity_changes_description": "エンティティの変更履歴を再構築します。同期処理で一部の変更が反映されていない場合に使用してください。" }, "fonts": { "fonts": "フォント", - "main_font": "メインフォント", + "main_font": "インターフェース テキスト", "font_family": "フォントファミリー", "size": "サイズ", - "note_tree_font": "ノートツリーフォント", - "note_detail_font": "ノート詳細フォント", - "monospace_font": "等幅(コード)フォント", - "not_all_fonts_available": "リストされているすべてのフォントが、お使いのシステムで利用できるとは限りません。", + "note_tree_font": "ノートツリー テキスト", + "note_detail_font": "ドキュメント テキスト", + "monospace_font": "等幅フォント", + "not_all_fonts_available": "リストにあるすべてのフォントが、お使いのシステムで利用できるとは限りません", "generic-fonts": "一般的なフォント", "sans-serif-system-fonts": "サンセリフのシステムフォント", "serif-system-fonts": "セリフのシステムフォント", @@ -842,7 +846,12 @@ "sans-serif": "サンセリフ", "monospace": "等幅", "system-default": "システムのデフォルト", - "theme_defined": "テーマが定義されました" + "theme_defined": "テーマが定義されました", + "custom_fonts": "カスタムフォントを使用", + "preview": "プレビュー", + "monospace_font_description": "コードノートとコードブロックに使用されます", + "size_relative_to_general": "サイズは一般的なフォントサイズを基準としています", + "apply_changes": "変更を適用するには再読み込みしてください" }, "max_content_width": { "title": "コンテンツ幅", @@ -852,20 +861,23 @@ "centerContent": "コンテンツを中央に配置" }, "theme": { - "title": "アプリのテーマ", - "theme_label": "テーマ", + "title": "ユーザーインターフェース", + "theme_label": "アプリケーションのテーマ", "override_theme_fonts_label": "テーマのフォントを上書き", - "auto_theme": "レガシー(システムの配色に従う)", - "light_theme": "レガシー(ライト)", - "dark_theme": "レガシー(ダーク)", - "triliumnext": "Trilium(システムの配色に従う)", - "triliumnext-light": "Trilium(ライト)", - "triliumnext-dark": "Trilium(ダーク)", + "auto_theme": "システムの配色に従う", + "light_theme": "ライト", + "dark_theme": "ダーク", + "triliumnext": "システムの配色に従う", + "triliumnext-light": "ライト", + "triliumnext-dark": "ダーク", "layout": "レイアウト", "layout-vertical-title": "垂直", "layout-horizontal-title": "水平", "layout-vertical-description": "ランチャーバーは左側(デフォルト)", - "layout-horizontal-description": "ランチャーバーはタブバーの下にあり、タブバーは全幅に。" + "layout-horizontal-description": "ランチャーバーはタブバーの下にあり、タブバーは全幅に。", + "modern_themes": "モダン", + "legacy_themes": "レガシー", + "custom_themes": "カスタム" }, "vim_key_bindings": { "use_vim_keybindings_in_code_notes": "Vimキーバインド", @@ -902,27 +914,30 @@ }, "search_engine": { "title": "検索エンジン", - "custom_search_engine_info": "カスタム検索エンジンは、名前とURLの両方を設定する必要があります。どちらも設定されていない場合、DuckDuckGoがデフォルトの検索エンジンとして使用されます。", - "predefined_templates_label": "定義済みの検索エンジンのテンプレート", + "custom_search_engine_info": "選択したテキストをウェブ上で検索する際に使用します。設定されていない場合は、DuckDuckGo が使用されます。", + "predefined_templates_label": "プリセット", "bing": "Bing", "baidu": "Baidu", "duckduckgo": "DuckDuckGo", "google": "Google", - "custom_name_label": "カスタム検索エンジンの名前", - "custom_name_placeholder": "カスタム検索エンジンの名前", - "custom_url_label": "カスタム検索エンジンのURLには、検索語句のプレースホルダーとして {keyword} を含める必要があります。", - "custom_url_placeholder": "検索エンジンの URL をカスタマイズ", - "save_button": "保存" + "custom_name_label": "名前", + "custom_name_placeholder": "検索エンジン名", + "custom_url_label": "URL", + "custom_url_placeholder": "検索エンジンの URL", + "save_button": "保存", + "custom_url_description": "検索語句の代わりに {keyword} を使用してください。" }, "tray": { "title": "システムトレイ", - "enable_tray": "トレイを有効にする (この変更を適用にするには、Triliumを再起動する必要があります)" + "enable_tray": "トレイアイコン", + "enable_tray_description": "この変更を有効にするには、Trilium を再起動する必要があります。" }, "heading_style": { "title": "見出しのスタイル", "plain": "プレーン", "underline": "下線", - "markdown": "Markdownスタイル" + "markdown": "Markdownスタイル", + "description": "テキストノート内の見出しの表示スタイル。" }, "highlights_list": { "title": "ハイライトリスト", @@ -951,20 +966,22 @@ "text_auto_read_only_size": { "title": "自動読み取り専用のサイズ", "description": "自動読み取り専用のノートサイズは、ノートが読み取り専用モード(パフォーマンス上の理由)で表示されるようになるサイズです。", - "label": "自動読み取り専用のサイズ(テキストノート)", + "label": "自動で読み取り専用になるサイズ", "unit": "文字" }, "code_auto_read_only_size": { "title": "自動読み取り専用のサイズ", "description": "自動読み取り専用のノートサイズは、ノートが読み取り専用モード(パフォーマンス上の理由)で表示されるようになるサイズです。", "unit": "文字", - "label": "自動読み取り専用のサイズ(コードノート)" + "label": "自動で読み取り専用になるサイズ" }, "custom_date_time_format": { - "title": "日付/時刻フォーマットのカスタム", + "title": "日付/時刻の形式", "description": "またはツールバーから挿入される日付と時刻のフォーマットをカスタマイズする。 利用可能なトークンについては Day.js ドキュメント を参照してください。", - "format_string": "文字列形式:", - "formatted_time": "日付/時刻形式:" + "format_string": "書式文字列", + "formatted_time": "日付/時刻の形式", + "description_short": "ツールバーから挿入する日付と時刻の形式をカスタマイズできます。", + "preview": "プレビュー: {{preview}}" }, "backup": { "automatic_backup": "自動バックアップ", @@ -980,7 +997,8 @@ "path": "パス", "database_backed_up_to": "データベースは{{backupFilePath}}にバックアップされました", "no_backup_yet": "バックアップがありません", - "download": "ダウンロード" + "download": "ダウンロード", + "title": "バックアップ" }, "password": { "wiki": "wiki", @@ -992,16 +1010,20 @@ "new_password_confirmation": "新パスワードの確認", "change_password": "パスワードの変更", "change_password_heading": "パスワードの変更", - "protected_session_timeout": "保護されたセッションのタイムアウト", - "protected_session_timeout_description": "保護されたセッションのタイムアウトは、保護されたセッションがブラウザのメモリから消去される時間です。これは、保護されたノートとの最後のやり取りから測定されます。参照", + "protected_session_timeout": "保護されたセッション", + "protected_session_timeout_description": "ブラウザのメモリからセッションが削除されるまでの非アクティブ時間。詳細はこちら", "for_more_info": "詳細はこちら。", - "protected_session_timeout_label": "保護されたセッションのタイムアウト:", + "protected_session_timeout_label": "セッションが自動で終了するまでの時間", "reset_confirmation": "パスワードをリセットすると、保護されているすべてのノートにアクセスできなくなります。本当にパスワードをリセットしますか?", "reset_success_message": "パスワードがリセットされました。新しいパスワードを設定してください", "set_password_heading": "パスワードの設定", "set_password": "パスワードの設定", "password_mismatch": "新しいパスワードが同じではありません。", - "password_changed_success": "パスワードが変更されました。OKを押すとTriliumがリロードされます。" + "password_changed_success": "パスワードが変更されました。OKを押すとTriliumがリロードされます。", + "change_password_description": "現在のパスワードを更新", + "reset_password": "パスワードをリセット", + "reset_password_description": "保護されたノートへのアクセス権を完全に失います", + "cancel": "キャンセル" }, "spellcheck": { "title": "スペルチェック", @@ -1017,17 +1039,19 @@ "related_description": "スペルチェック対応言語とカスタム辞書を設定します。" }, "sync_2": { - "config_title": "同期設定", - "server_address": "サーバーインスタンスのアドレス", - "timeout": "同期タイムアウト", - "proxy_label": "同期プロキシサーバー(任意)", + "config_title": "同期サーバー", + "server_address": "サーバーアドレス", + "timeout": "接続タイムアウト", + "proxy_label": "プロキシサーバー", "save": "保存", "help": "ヘルプ", - "test_title": "同期のテスト", - "test_description": "これは同期サーバとの接続とハンドシェイクをテストします。同期サーバーが初期化されていない場合、ローカルドキュメントと同期するように設定します。", + "test_title": "接続テスト", + "test_description": "同期サーバーへの接続をテストします。初期化されていない場合は、同期の設定を行います。", "test_button": "同期試行", "handshake_failed": "同期サーバーのハンドシェイクに失敗しました。エラー: {{message}}", - "timeout_description": "同期接続が遅い場合に、接続を諦めるまでの待機時間。ネットワークが不安定な場合は、この時間を長く設定してください。" + "timeout_description": "接続が遅い場合に接続を断念するまでの待機時間。", + "server_address_description": "同期先の Trilium サーバーの URL。", + "proxy_description": "システムのプロキシを使用する場合は空欄のままにしてください(デスクトップ版のみ)。すべてのプロキシをバイパスするには \"noproxy\" を使用してください。" }, "api_log": { "close": "閉じる" @@ -1346,7 +1370,8 @@ "title": "固定", "description": "編集ツールは「書式設定」のリボンタブに表示されます。" }, - "multiline-toolbar": "ツールバーが収まりきらない場合は、複数行で表示する。" + "multiline-toolbar": "ツールバーが収まりきらない場合は複数行で表示", + "toolbar_style": "ツールバーのスタイル" } }, "electron_context_menu": { @@ -1367,7 +1392,7 @@ "days": "日" }, "share": { - "title": "共有設定", + "title": "共有", "redirect_bare_domain": "ネイキッドドメインを共有ページにリダイレクト", "redirect_bare_domain_description": "匿名のユーザーをログイン画面ではなく共有ページにリダイレクトします", "show_login_link_description": "共有ページの下部にログインリンクを追加", @@ -1412,12 +1437,12 @@ "message_macos": "TriliumNext は現在、Rosetta 2による翻訳環境で実行されています。つまり、Apple Silicon MacではIntel (x64)バージョンを使用していることになります。これはパフォーマンスとバッテリー寿命に重大な影響を及ぼします。" }, "editorfeatures": { - "emoji_completion_enabled": "絵文字のオートコンプリートを有効", - "note_completion_enabled": "ノートのオートコンプリートを有効", - "emoji_completion_description": "有効にすると、「:」に続けて絵文字の名前を入力することで、テキストに絵文字を簡単に挿入できます。", - "note_completion_description": "有効にすると、「@」 に続けてノートのタイトルを入力することで、ノートへのリンクを作成できます。", - "slash_commands_enabled": "スラッシュコマンドを有効", - "slash_commands_description": "有効にすると、改行や見出しの挿入などの編集コマンドは、「/」 と入力して切り替えることができます。", + "emoji_completion_enabled": "絵文字のオートコンプリート", + "note_completion_enabled": "ノートのオートコンプリート", + "emoji_completion_description": "絵文字は \":\" の後に絵文字の名前を入力することで、テキストに絵文字を簡単に挿入できます。", + "note_completion_description": "ノートへのリンクは \"@\" の後にノートのタイトルを入力することで作成できます。", + "slash_commands_enabled": "スラッシュコマンド", + "slash_commands_description": "改行や見出しの挿入などの編集コマンドは \"/\" を入力して切り替えることができます。", "title": "機能" }, "table_context_menu": { @@ -1556,11 +1581,11 @@ }, "database_anonymization": { "title": "データベースの匿名化", - "full_anonymization": "完全匿名化", - "full_anonymization_description": "この操作により、データベースの新しいコピーが作成され、匿名化されます(すべてのノートの内容を削除し、構造と一部の非機密メタデータのみを残します)。これにより、個人データが漏洩する心配なく、デバッグ目的でオンライン共有できます。", + "full_anonymization": "完全な匿名化", + "full_anonymization_description": "ノートの内容をすべて削除し、構造と機密性の低いメタデータのみを残したデータベースのコピーを作成します。問題のデバッグ時にオンラインで共有しても安全です。", "save_fully_anonymized_database": "完全に匿名化されたデータベースを保存", "light_anonymization": "軽い匿名化", - "light_anonymization_description": "この操作により、データベースの新しいコピーが作成され、軽い匿名化が適用されます。具体的には、すべてのノートの内容のみが削除され、タイトルと属性はそのまま残ります。さらに、カスタムJSフロントエンド/バックエンドスクリプトノートとカスタムウィジェットもそのまま残ります。これにより、問題のデバッグのためのコンテキストがより多く提供されます。", + "light_anonymization_description": "ノートの内容は削除しますが、タイトル、属性、カスタムスクリプト/ウィジェットは残します。デバッグのためのコンテキストがより多く提供されます。", "choose_anonymization": "完全に匿名化したデータベースを提供するか、軽く匿名化したデータベースを提供するかは、あなた自身が決めることができます。完全に匿名化されたDBであっても非常に有用ですが、場合によっては軽く匿名化されたDBの方がバグの特定と修正のプロセスを速めることができます。", "save_lightly_anonymized_database": "軽く匿名化されたデータベースを保存", "existing_anonymized_databases": "既存の匿名化データベース", @@ -1569,14 +1594,17 @@ "error_creating_anonymized_database": "匿名化データベースの作成に失敗しました。詳細はバックエンドログを確認してください", "successfully_created_fully_anonymized_database": "完全に匿名化されたデータベースを {{anonymizedFilePath}} に作成", "successfully_created_lightly_anonymized_database": "軽く匿名化されたデータベースを {{anonymizedFilePath}} に作成", - "no_anonymized_database_yet": "匿名化されたデータベースはまだありません。" + "no_anonymized_database_yet": "匿名化されたデータベースはまだありません。", + "description": "問題のデバッグ時に開発者と共有するために、個人データを公開することなく、データベースの匿名化されたコピーを作成します。" }, "database_integrity_check": { "title": "データベースの整合性チェック", "check_button": "データベースの整合性をチェック", "checking_integrity": "データベースの整合性をチェックしています...", "integrity_check_succeeded": "整合性チェックに成功 - 問題は見つかりませんでした。", - "integrity_check_failed": "整合性チェックに失敗: {{results}}" + "integrity_check_failed": "整合性チェックに失敗: {{results}}", + "check_integrity_label": "データベースの整合性チェック", + "check_integrity_description": "SQLite レベルでデータベースが破損していないことを確認します。" }, "code-editor-options": { "title": "エディター" @@ -1723,14 +1751,18 @@ "find_and_fix_button": "一貫性の問題を見つけて修正する", "finding_and_fixing_message": "一貫性の問題を見つけて修正中…", "title": "一貫性をチェック", - "issues_fixed_message": "発見された可能性のある一貫性の問題はすべて修正されました。" + "issues_fixed_message": "発見された可能性のある一貫性の問題はすべて修正されました。", + "find_and_fix_label": "整合性の問題を検出して修正", + "find_and_fix_description": "データベース内のデータ整合性の問題をスキャンし、自動的に修復します。" }, "vacuum_database": { "title": "データベースのバキューム", "description": "これによりデータベースが再構築され、通常はデータベースファイルのサイズが小さくなります。実際のデータは変更されません。", "button_text": "データベースをバキューム", "vacuuming_database": "データベースのバキュームを実行中...", - "database_vacuumed": "データベースのバキューム処理が完了しました" + "database_vacuumed": "データベースのバキューム処理が完了しました", + "vacuum_label": "データベースのバキューム", + "vacuum_description": "データベースを再構築してファイルサイズを削減します。データは変更されません。" }, "ribbon": { "promoted_attributes_message": "プロモート属性がノートに存在する場合、プロモート属性のリボンタブが自動的に開きます", @@ -1738,12 +1770,12 @@ "widgets": "リボンウィジェット" }, "ui-performance": { - "enable-motion": "トランジションとアニメーションを有効にする", - "enable-shadows": "影を有効にする", - "enable-backdrop-effects": "メニュー、ポップアップ、パネルの背景効果を有効にする", + "enable-motion": "画面遷移とアニメーション", + "enable-shadows": "影", + "enable-backdrop-effects": "メニュー、ポップアップ、パネルの背景効果", "title": "パフォーマンス", - "enable-smooth-scroll": "スムーズスクロールを有効にする", - "app-restart-required": "(変更を有効にするにはアプリケーションの再起動が必要です)" + "enable-smooth-scroll": "スムーズスクロール", + "app-restart-required": "アプリの再起動が必要です" }, "code_mime_types": { "title": "ドロップダウンで利用可能なMIMEタイプ", @@ -1752,35 +1784,43 @@ "tooltip_code_note_syntax": "コードノート" }, "attachment_erasure_timeout": { - "attachment_erasure_timeout": "添付ファイル消去のタイムアウト", - "erase_attachments_after": "使用されていない添付ファイルを消去する期間:", - "manual_erasing_description": "手動で消去をトリガーすることもできます (上記で定義したタイムアウトを考慮せずに):", - "erase_unused_attachments_now": "使用されていない添付ノートを今すぐ消去", - "unused_attachments_erased": "使用されていない添付ファイルは削除されました。" + "attachment_erasure_timeout": "未使用の添付ファイル", + "erase_attachments_after": "未使用の添付ファイルを削除するまでの期間", + "manual_erasing_description": "上記のタイムアウト設定を無視して、手動で削除を実行する。", + "erase_unused_attachments_now": "未使用の添付ノートを今すぐ削除", + "unused_attachments_erased": "使用されていない添付ファイルは削除されました。", + "description": "どのノートからも参照されていない添付ファイルは未使用とみなされ、一定期間経過後に自動的に削除されます。", + "erase_attachments_after_description": "未使用の添付ファイルが完全に削除されるまでの時間。" }, "network_connections": { - "network_connections_title": "ネットワーク接続", - "check_for_updates": "アップデートを自動的に確認する" + "network_connections_title": "ネットワーク", + "check_for_updates": "アップデートを自動的に確認する", + "check_for_updates_description": "GitHub で新しいバージョンをチェックし、利用可能な場合はグローバルメニューに通知を表示します。" }, "note_erasure_timeout": { - "note_erasure_timeout_title": "ノート消去のタイムアウト", - "erase_notes_after": "ノートを消去する間隔:", - "manual_erasing_description": "手動で消去をトリガーすることもできます (上記で定義したタイムアウトを考慮せずに):", + "note_erasure_timeout_title": "削除されたノート", + "erase_notes_after": "ノートを削除するまでの期間", + "manual_erasing_description": "上記のタイムアウト設定を無視して、手動で削除を実行する。", "erase_deleted_notes_now": "削除したノートを今すぐ消去", - "deleted_notes_erased": "削除されたノートは消去されました。" + "deleted_notes_erased": "削除されたノートは消去されました。", + "description": "削除されたノートは、最初は削除済みとしてマークされるだけで最近の変更から復元できます。一定期間が経過すると、完全に削除されます。", + "erase_notes_after_description": "削除されたノートが完全に削除されるまでの時間。" }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "ノートの変更履歴の記録間隔", "note_revisions_snapshot_description": "ノートの変更履歴の記録間隔は、そのノートに対して新しい変更が作成されるまでの時間です。詳細については、wiki をご覧ください。", - "snapshot_time_interval_label": "ノートの変更履歴が記憶される時間:" + "snapshot_time_interval_label": "スナップショットの間隔", + "note_revisions_snapshot_description_short": "新しいノートの変更履歴が作成されるまでの時間。" }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "ノートの変更履歴の記録制限", "note_revisions_snapshot_limit_description": "ノートの変更履歴の記録制限とは、各ノートに保存できる変更履歴の最大数を指します。-1 は制限なし、0 はすべての変更履歴を削除することを意味します。#versioningLimit ラベルを使用して、1 つのノートの最大変更数を設定できます。", - "snapshot_number_limit_label": "ノートの変更履歴の記録数の制限:", + "snapshot_number_limit_label": "最大変更履歴数", "snapshot_number_limit_unit": "スナップショット", "erase_excess_revision_snapshots": "余分な変更履歴を今すぐ消去", - "erase_excess_revision_snapshots_prompt": "余分な変更履歴が消去されました。" + "erase_excess_revision_snapshots_prompt": "余分な変更履歴が消去されました。", + "note_revisions_snapshot_limit_description_short": "ノートごとの最大変更履歴数。無制限の場合は -1、無効にする場合は 0 を指定してください。", + "erase_excess_revision_snapshots_description": "すべてのノートで、制限を超えた変更履歴を削除します。" }, "editability_select": { "note_is_read_only": "ノートは読み取り専用ですが、ボタンをクリックすると編集できます。", @@ -1916,7 +1956,9 @@ "related_code_notes": "コードノートの配色", "ui": "ユーザーインターフェース", "ui_old_layout": "旧レイアウト", - "ui_new_layout": "新しいレイアウト" + "ui_new_layout": "新しいレイアウト", + "ui_layout_style": "レイアウトスタイル", + "ui_layout_orientation": "ランチャーバーの方向" }, "units": { "percentage": "%" @@ -2334,5 +2376,24 @@ "processing_complete": "OCR 処理が完了しました。", "text_filtered_low_confidence": "OCR は {{confidence}}% の信頼度でテキストを検出しましたが、最小しきい値が {{threshold}}% であるため、破棄されました。", "open_media_settings": "設定を開く" + }, + "revisions": { + "title": "ノートの変更履歴" + }, + "database": { + "title": "データベース" + }, + "search": { + "title": "検索", + "fuzzy_matching_label": "検索時の入力ミス許容度", + "fuzzy_matching_description": "クイック検索とフル検索に影響します。完全一致しない場合でも類似語を検索します。", + "autocomplete_fuzzy_label": "オートコンプリート時の入力ミス許容度", + "autocomplete_fuzzy_description": "ノートへのジャンプとノートの選択に影響します。処理速度は低下しますが、入力ミスを許容します。" + }, + "text_editor": { + "title": "エディター" + }, + "link": { + "failed_to_open": "'{{- href}}' のリンクを開けなせんでした: {{- message}}" } } From f0293b7f0742b42f062f8af6e8988e6426b1e377 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 03:56:43 +0200 Subject: [PATCH 170/203] Added translation using Weblate (Uyghur) --- apps/client/src/translations/ug/translation.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/client/src/translations/ug/translation.json diff --git a/apps/client/src/translations/ug/translation.json b/apps/client/src/translations/ug/translation.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/apps/client/src/translations/ug/translation.json @@ -0,0 +1 @@ +{} From 3c0d5b761435ce6300ac9906f6ffe941c25cb524 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 03:56:45 +0200 Subject: [PATCH 171/203] Added translation using Weblate (Uyghur) --- docs/README-ug.md | 337 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 docs/README-ug.md diff --git a/docs/README-ug.md b/docs/README-ug.md new file mode 100644 index 0000000000..0da3a37bf9 --- /dev/null +++ b/docs/README-ug.md @@ -0,0 +1,337 @@ +
+ Special thanks to:
+ + Warp sponsorship
+ Warp, built for coding with multiple AI agents
+
+ Available for macOS, Linux and Windows +
+ +
+ +# Trilium Notes + +![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran) +![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran)\ +![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/trilium) +![GitHub Downloads (all assets, all +releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\ +[![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) +[![Translation +status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/) + + + +[Chinese (Simplified Han script)](./README-ZH_CN.md) | [Chinese (Traditional Han +script)](./README-ZH_TW.md) | [English](../README.md) | [French](./README-fr.md) +| [German](./README-de.md) | [Greek](./README-el.md) | [Italian](./README-it.md) +| [Japanese](./README-ja.md) | [Romanian](./README-ro.md) | +[Spanish](./README-es.md) + + +Trilium Notes is a free and open-source, cross-platform hierarchical note taking +application with focus on building large personal knowledge bases. + +Trilium Screenshot + +## ⏬ Download +- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) – + stable version, recommended for most users. +- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – + unstable development version, updated daily with the latest features and + fixes. + +## 📚 Documentation + +**Visit our comprehensive documentation at +[docs.triliumnotes.org](https://docs.triliumnotes.org/)** + +Our documentation is available in multiple formats: +- **Online Documentation**: Browse the full documentation at + [docs.triliumnotes.org](https://docs.triliumnotes.org/) +- **In-App Help**: Press `F1` within Trilium to access the same documentation + directly in the application +- **GitHub**: Navigate through the [User Guide](./User%20Guide/User%20Guide/) in + this repository + +### Quick Links +- [Getting Started Guide](https://docs.triliumnotes.org/) +- [Installation Instructions](https://docs.triliumnotes.org/user-guide/setup) +- [Docker + Setup](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker) +- [Upgrading + TriliumNext](https://docs.triliumnotes.org/user-guide/setup/upgrading) +- [Basic Concepts and + Features](https://docs.triliumnotes.org/user-guide/concepts/notes) +- [Patterns of Personal Knowledge + Base](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge) + +## 🎁 Features + +* Notes can be arranged into arbitrarily deep tree. Single note can be placed + into multiple places in the tree (see + [cloning](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning)) +* Rich WYSIWYG note editor including e.g. tables, images and + [math](https://docs.triliumnotes.org/user-guide/note-types/text) with markdown + [autoformat](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting) +* Support for editing [notes with source + code](https://docs.triliumnotes.org/user-guide/note-types/code), including + syntax highlighting +* Fast and easy [navigation between + notes](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation), + full text search and [note + hoisting](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting) +* Seamless [note + versioning](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions) +* Note + [attributes](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes) + can be used for note organization, querying and advanced + [scripting](https://docs.triliumnotes.org/user-guide/scripts) +* UI available in English, German, Spanish, French, Romanian, and Chinese + (simplified and traditional) +* Direct [OpenID and TOTP + integration](https://docs.triliumnotes.org/user-guide/setup/server/mfa) for + more secure login +* [Synchronization](https://docs.triliumnotes.org/user-guide/setup/synchronization) + with self-hosted sync server + * there are [3rd party services for hosting synchronisation + server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) +* [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) + (publishing) notes to public internet +* Strong [note + encryption](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) + with per-note granularity +* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type + "canvas") +* [Relation + maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and + [note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map) + for visualizing notes and their relations +* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/) +* [Geo maps](https://docs.triliumnotes.org/user-guide/collections/geomap) with + location pins and GPX tracks +* [Scripting](https://docs.triliumnotes.org/user-guide/scripts) - see [Advanced + showcases](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases) +* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) for + automation +* Scales well in both usability and performance upwards of 100 000 notes +* Touch optimized [mobile + frontend](https://docs.triliumnotes.org/user-guide/setup/mobile-frontend) for + smartphones and tablets +* Built-in [dark + theme](https://docs.triliumnotes.org/user-guide/concepts/themes), support for + user themes +* [Evernote](https://docs.triliumnotes.org/user-guide/concepts/import-export/evernote) + and [Markdown import & + export](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown) +* [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) for + easy saving of web content +* Customizable UI (sidebar buttons, user-defined widgets, ...) +* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), + along with a Grafana Dashboard. + +✨ Check out the following third-party resources/communities for more TriliumNext +related goodies: + +- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party + themes, scripts, plugins and more. +- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more. + +## ❓Why TriliumNext? + +The original Trilium developer ([Zadam](https://github.com/zadam)) has +graciously given the Trilium repository to the community project which resides +at https://github.com/TriliumNext + +### ⬆️Migrating from Zadam/Trilium? + +There are no special migration steps to migrate from a zadam/Trilium instance to +a TriliumNext/Trilium instance. Simply [install +TriliumNext/Trilium](#-installation) as usual and it will use your existing +database. + +Versions up to and including +[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are +compatible with the latest zadam/trilium version of +[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later +versions of TriliumNext/Trilium have their sync versions incremented which +prevents direct migration. + +## 💬 Discuss with us + +Feel free to join our official conversations. We would love to hear what +features, suggestions, or issues you may have! + +- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous + discussions.) + - The `General` Matrix room is also bridged to + [XMPP](xmpp:discuss@trilium.thisgreat.party?join) +- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For + asynchronous discussions.) +- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug + reports and feature requests.) + +## 🏗 Installation + +### Windows / MacOS + +Download the binary release for your platform from the [latest release +page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package +and run the `trilium` executable. + +### Linux + +If your distribution is listed in the table below, use your distribution's +package. + +[![Packaging +status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions) + +You may also download the binary release for your platform from the [latest +release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the +package and run the `trilium` executable. + +TriliumNext is also provided as a Flatpak, but not yet published on FlatHub. + +### Browser (any OS) + +If you use a server installation (see below), you can directly access the web +interface (which is almost identical to the desktop app). + +Currently only the latest versions of Chrome & Firefox are supported (and +tested). + +### Mobile + +To use TriliumNext on a mobile device, you can use a mobile web browser to +access the mobile interface of a server installation (see below). + +See issue https://github.com/TriliumNext/Trilium/issues/4962 for more +information on mobile app support. + +If you prefer a native Android app, you can use +[TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid). +Report bugs and missing features at [their +repository](https://github.com/FliegendeWurst/TriliumDroid). Note: It is best to +disable automatic updates on your server installation (see below) when using +TriliumDroid since the sync version must match between Trilium and TriliumDroid. + +### Server + +To install TriliumNext on your own server (including via Docker from +[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server +installation docs](https://docs.triliumnotes.org/user-guide/setup/server). + + +## 💻 Contribute + +### Translations + +If you are a native speaker, help us translate Trilium by heading over to our +[Weblate page](https://hosted.weblate.org/engage/trilium/). + +Here's the language coverage we have so far: + +[![Translation +status](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/) + +### Code + +Download the repository, install dependencies using `pnpm` and then run the +server (available at http://localhost:8080): +```shell +git clone https://github.com/TriliumNext/Trilium.git +cd Trilium +pnpm install +pnpm run server:start +``` + +### Documentation + +Download the repository, install dependencies using `pnpm` and then run the +environment required to edit the documentation: +```shell +git clone https://github.com/TriliumNext/Trilium.git +cd Trilium +pnpm install +pnpm edit-docs:edit-docs +``` + +### Building the Executable +Download the repository, install dependencies using `pnpm` and then build the +desktop app for Windows: +```shell +git clone https://github.com/TriliumNext/Trilium.git +cd Trilium +pnpm install +pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32 +``` + +For more details, see the [development +docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide). + +### Developer Documentation + +Please view the [documentation +guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) +for details. If you have more questions, feel free to reach out via the links +described in the "Discuss with us" section above. + +## 👏 Shoutouts + +* [zadam](https://github.com/zadam) for the original concept and implementation + of the application. +* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the + application icon. +* [nriver](https://github.com/nriver) for his work on internationalization. +* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas. +* [antoniotejada](https://github.com/nriver) for the original syntax highlight + widget. +* [Dosu](https://dosu.dev/) for providing us with the automated responses to + GitHub issues and discussions. +* [Tabler Icons](https://tabler.io/icons) for the system tray icons. + +Trilium would not be possible without the technologies behind it: + +* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - the visual editor behind + text notes. We are grateful for being offered a set of the premium features. +* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with + support for huge amount of languages. +* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite + whiteboard used in Canvas notes. +* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the + mind map functionality. +* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical + maps. +* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive + table used in collections. +* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library + without real competition. +* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library. + Used in [relation + maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and + [link + maps](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map) + +## 🤝 Support + +Trilium is built and maintained with [hundreds of hours of +work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your +support keeps it open-source, improves features, and covers costs such as +hosting. + +Consider supporting the main developer +([eliandoran](https://github.com/eliandoran)) of the application via: + +- [GitHub Sponsors](https://github.com/sponsors/eliandoran) +- [PayPal](https://paypal.me/eliandoran) +- [Buy Me a Coffee](https://buymeacoffee.com/eliandoran) + +## 🔑 License + +Copyright 2017-2025 zadam, Elian Doran, and other contributors + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. From 4b9d2537451fd3442480e0c95d9fbb947d1943b6 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 03:56:46 +0200 Subject: [PATCH 172/203] Added translation using Weblate (Uyghur) --- apps/server/src/assets/translations/ug/server.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/server/src/assets/translations/ug/server.json diff --git a/apps/server/src/assets/translations/ug/server.json b/apps/server/src/assets/translations/ug/server.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/apps/server/src/assets/translations/ug/server.json @@ -0,0 +1 @@ +{} From 1442041cda35980cc070887df9c78f2878b05d16 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 03:56:47 +0200 Subject: [PATCH 173/203] Added translation using Weblate (Uyghur) --- apps/website/src/translations/ug/translation.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/website/src/translations/ug/translation.json diff --git a/apps/website/src/translations/ug/translation.json b/apps/website/src/translations/ug/translation.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/apps/website/src/translations/ug/translation.json @@ -0,0 +1 @@ +{} From a0e9082364ed49cee2417910d2263e258b3f6550 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 04:47:19 +0200 Subject: [PATCH 174/203] Translated using Weblate (Uyghur) Currently translated at 1.4% (28 of 1880 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ug/ --- .../src/translations/ug/translation.json | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/ug/translation.json b/apps/client/src/translations/ug/translation.json index 0967ef424b..5167ad0305 100644 --- a/apps/client/src/translations/ug/translation.json +++ b/apps/client/src/translations/ug/translation.json @@ -1 +1,46 @@ -{} +{ + "about": { + "title": "ھەققىدە Trilium Notes", + "homepage": "پروگرامما باش بېتى:", + "app_version": "نۇسخىسىApp:", + "db_version": "ساندان نۇسخىسى:", + "sync_version": "ماسلاش نۇسخىسى:", + "build_date": "قۇرۇلغان ۋاقىت:", + "build_revision": "قۇرۇلۇش نۇسخىسى:", + "data_directory": "سانلىق مەلۇمات مۇندەرىجىسى:" + }, + "toast": { + "critical-error": { + "title": "ئېغىر خاتالىق", + "message": "قوللىنىشچان پروگراممىنىڭ قوزغىلىشىغا توسالغۇ بولىدىغان ئېغىر خاتالىق كۆرۈلدى:\n\n{{message}}\n\nبۇ ئادەتتە script نىڭ كۈتۈلمىگەن خاتالىقىدىن كېلىپ چىقىدۇ. پروگراممىنى بىخەتەر ھالەتتە (safe mode) قوزغىتىپ مەسىلىنى ھەل قىلىپ بېقىڭ。" + }, + "widget-error": { + "title": "كىچىك زاپچاسلار دەسلەپكى قەدەمدە مەغلۇپ بولدى", + "message-custom": "ID نومۇرى \"{{id}}\"، تېمىسى \"{{title}}\" بولغان خاتىرىدىن كەلگەن ئۆزلەشتۈرۈلگەن ۋىجېت (Custom widget) تۆۋەندىكى سەۋەب تۈپەيلىدىن قوزغىتىلمىدى:\n\n{{message}}", + "message-unknown": "نامەلۇم كىچىك زاپچاسلار تۆۋەندىكى سەۋەبلەر تۈپەيلىدىن دەسلەپكى قەدەمدە بولالمايدۇ:\n\n{{message}}" + }, + "bundle-error": { + "title": "scriptنى يۈكلەش مەغلۇپ بولدى", + "message": "scriptنى ئىجرا قىلغىلى بولمىدى:\n\n{{message}}" + }, + "widget-list-error": { + "title": "serverدىن تىزىملىكىنى ئالغىلى بولمىدى" + }, + "widget-render-error": { + "title": "ئۆزلەشتۈرۈلگەن React ۋىجېتىنى تەقدىم قىلىش (Render) مەغلۇپ بولدى" + }, + "widget-missing-parent": "ئۆزگەرتىلگەن كىچىك زاپچاستا زۆرۈر بولغان’{{property}}‘ خاسلىقى بەلگىلەنمىگەن.\n\nئەگەر بۇ سكريپت UI ئېلېمېنتلىرىسىز ئىجرا بولۇشقا مەجبۇر بولسا، \"#run=frontendStartup\" نى ئىشلىتىڭ.", + "open-script-note": "script خاتىرىنى ئېچىش", + "scripting-error": "Custom script خاتالىقى: {{title}}" + }, + "add_link": { + "add_link": "ئۇلىنىش قوشۇش", + "help_on_links": "ئۇلىنىش ياردىمى", + "note": "خاتىرە", + "search_note": "خاتىرىنى نامى بويىچە ئىزدەش", + "link_title_mirrors": "ئۇلىنىش تېمىسى خاتىرىنىڭ ھازىرقى تېمىسى بىلەن ماس قەدەم بولىدۇ", + "link_title_arbitrary": "ئۇلىنىش تېمىسىنى خالىغانچە ئۆزگەرتىشكە بولىدۇ", + "link_title": "ئۇلىنىش تېمىسى", + "button_add_link": "ئۇلىنىش قوشۇش" + } +} From 5ad0cb2b4b2835e9a4dfad5f915e90ea9f410ac5 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 08:39:37 +0200 Subject: [PATCH 175/203] Translated using Weblate (Uyghur) Currently translated at 3.1% (5 of 158 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ug/ --- apps/website/src/translations/ug/translation.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/website/src/translations/ug/translation.json b/apps/website/src/translations/ug/translation.json index 0967ef424b..a7fb8b96c0 100644 --- a/apps/website/src/translations/ug/translation.json +++ b/apps/website/src/translations/ug/translation.json @@ -1 +1,11 @@ -{} +{ + "get-started": { + "title": "باشلاش", + "architecture": "قۇرۇلما:", + "older_releases": "كونا نۇسخىلىرىنى كۆرۈش" + }, + "hero_section": { + "title": "ئويلىرىڭىزنى رەتلەڭ. شەخسىي بىلىم ئامبىرىڭىزنى قۇرۇپ چىقىڭ.", + "subtitle": "Trilium — خاتىرە قالدۇرۇش ۋە شەخسىي بىلىم ئامبىرىنى رەتلەش ئۈچۈن لايىھەلەنگەن ئوچۇق كودلۇق ھەل قىلىش چارىسىدۇر. ئۇنى ئۆزىڭىزنىڭ ئۈستەلئۈستى كومپيۇتېرىدا بىۋاسىتە ئىشلىتەلەيسىز ياكى ئۆزىڭىز قۇرغان (self-hosted) مۇلازىمېتىر بىلەن ماسقەدەملەپ، خاتىرىلىرىڭىزنى ھەر ۋاقىت، ھەر قانداق يەردە زىيارەت قىلالايسىز." + } +} From a82d9dad1bb22abee44830bad3a4456ac56e2949 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 08:47:49 +0200 Subject: [PATCH 176/203] Translated using Weblate (Uyghur) Currently translated at 6.7% (8 of 119 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ug/ --- docs/README-ug.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/README-ug.md b/docs/README-ug.md index 0da3a37bf9..1dd96d5ed3 100644 --- a/docs/README-ug.md +++ b/docs/README-ug.md @@ -29,19 +29,20 @@ script)](./README-ZH_TW.md) | [English](../README.md) | [French](./README-fr.md) [Spanish](./README-es.md) -Trilium Notes is a free and open-source, cross-platform hierarchical note taking -application with focus on building large personal knowledge bases. +Trilium Notes بولسا ھەقسىز، ئوچۇق كودلۇق، سىستېما ھالقىغان، قاتلاملىق خاتىرە +قالدۇرۇش ئەپى بولۇپ، ئۇ ئاساسلىقى چوڭ تىپتىكى شەخسىي بىلىم ئامبىرى قۇرۇشقا +ئەھمىيەت بېرىدۇ. Trilium Screenshot -## ⏬ Download +## چۈشۈرۈش⏬ - [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) – stable version, recommended for most users. - [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – unstable development version, updated daily with the latest features and fixes. -## 📚 Documentation +## ھۆججەتلەر📚 **Visit our comprehensive documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)** From d147bbe63de5a830e14509650a2c7f14ac5f0f90 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 08:37:03 +0200 Subject: [PATCH 177/203] Translated using Weblate (Uyghur) Currently translated at 1.4% (6 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ug/ --- apps/server/src/assets/translations/ug/server.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/ug/server.json b/apps/server/src/assets/translations/ug/server.json index 0967ef424b..ffc7858ad1 100644 --- a/apps/server/src/assets/translations/ug/server.json +++ b/apps/server/src/assets/translations/ug/server.json @@ -1 +1,10 @@ -{} +{ + "keyboard_actions": { + "back-in-note-history": "تارىختىكى ئالدىنقى خاتىرىگە قايتىش", + "forward-in-note-history": "تارىختىكى كېيىنكى خاتىرىگە ئۆتۈش", + "open-jump-to-note-dialog": "\"خاتىرىگە تېز ئۆتۈش\" كۆزنىكىنى ئېچىش", + "open-command-palette": "بۇيرۇق تىزىملىكىنى ئېچىش", + "quick-search": "تېز ئىزدەشنى قوزغىتىش", + "search-in-subtree": "ھازىرقى خاتىرىنىڭ تارماقلىرىدىن ئىزدەش" + } +} From 48e90396bd0025b0023791e9bdde6c4d5a2ae72d Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 08:46:05 +0200 Subject: [PATCH 178/203] Translated using Weblate (Uyghur) Currently translated at 8.4% (159 of 1880 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ug/ --- .../src/translations/ug/translation.json | 169 +++++++++++++++++- 1 file changed, 161 insertions(+), 8 deletions(-) diff --git a/apps/client/src/translations/ug/translation.json b/apps/client/src/translations/ug/translation.json index 5167ad0305..7d3a4d0e32 100644 --- a/apps/client/src/translations/ug/translation.json +++ b/apps/client/src/translations/ug/translation.json @@ -1,8 +1,8 @@ { "about": { - "title": "ھەققىدە Trilium Notes", + "title": "Trilium خاتىرە ھەققىدە", "homepage": "پروگرامما باش بېتى:", - "app_version": "نۇسخىسىApp:", + "app_version": "App نۇسخىسى:", "db_version": "ساندان نۇسخىسى:", "sync_version": "ماسلاش نۇسخىسى:", "build_date": "قۇرۇلغان ۋاقىت:", @@ -34,13 +34,166 @@ "scripting-error": "Custom script خاتالىقى: {{title}}" }, "add_link": { - "add_link": "ئۇلىنىش قوشۇش", - "help_on_links": "ئۇلىنىش ياردىمى", + "add_link": "ئۇلانما قوشۇش", + "help_on_links": "ئۇلانما ياردىمى", "note": "خاتىرە", "search_note": "خاتىرىنى نامى بويىچە ئىزدەش", - "link_title_mirrors": "ئۇلىنىش تېمىسى خاتىرىنىڭ ھازىرقى تېمىسى بىلەن ماس قەدەم بولىدۇ", - "link_title_arbitrary": "ئۇلىنىش تېمىسىنى خالىغانچە ئۆزگەرتىشكە بولىدۇ", - "link_title": "ئۇلىنىش تېمىسى", - "button_add_link": "ئۇلىنىش قوشۇش" + "link_title_mirrors": "ئۇلانما تېمىسى خاتىرىنىڭ ھازىرقى تېمىسى بىلەن ماس قەدەم بولىدۇ", + "link_title_arbitrary": "ئۇلانما تېمىسىنى خالىغانچە ئۆزگەرتىشكە بولىدۇ", + "link_title": "ئۇلانما تېمىسى", + "button_add_link": "ئۇلانما قوشۇش" + }, + "branch_prefix": { + "edit_branch_prefix": "شاخچە ئالدى قوشۇمچىسىنى تەھرىرلەش", + "edit_branch_prefix_multiple": "{{count}} دانە شاخچىنىڭ ئالدى قوشۇمچىسىنى تەھرىرلەش", + "help_on_tree_prefix": "دەرەخ ئالدى قوشۇمچىسى ياردىمى", + "prefix": "ئالدى قوشۇمچە:· ", + "save": "ساقلاش", + "branch_prefix_saved": "شاخچە ئالدى قوشۇمچىسى ساقلاندى.", + "branch_prefix_saved_multiple": "{{count}} دانە شاخچىغا ئالدى قوشۇمچە ساقلاندى.", + "affected_branches": "تەسىرگە ئۇچرىغان شاخچىلار {{count}}:" + }, + "bulk_actions": { + "bulk_actions": "تۈركۈملاپ بىر تەرەپ قىلىش", + "affected_notes": "تەسىرگە ئۇچرايدىغان خاتىرىلەر", + "include_descendants": "تاللانغان خاتىرىلەرنىڭ تارماق خاتىرىلىرىنى ئۆز ئىچىگە ئالسۇن", + "available_actions": "ئىشلەتكىلى بولىدىغان مەشغۇلاتلار", + "chosen_actions": "تاللانغان مەشغۇلاتلار", + "execute_bulk_actions": "تۈركۈملەپ بىر تەرەپ قىلىشنى ئىجرا قىلىش", + "bulk_actions_executed": "تۈركۈملەپ بىر تەرەپ قىلىش مۇۋەپپەقىيەتلىك تاماملاندى.", + "none_yet": "ھازىرچە مەشغۇلات يوق... ئۈستىدىكى مەشغۇلاتتىن تاللاڭ.", + "labels": "بەلگە", + "relations": "مۇناسىۋەتلەر", + "notes": "خاتىرىلەر", + "other": "باشقىلار" + }, + "clone_to": { + "clone_notes_to": "خاتىرىلەرنى كۆچۈرۈش...", + "help_on_links": "ئۇلانما ياردىمى", + "notes_to_clone": "كۆچۈرۈلىدىغان خاتىرىلەر", + "target_parent_note": "نىشان تۈپ خاتىرە", + "search_for_note_by_its_name": "نامى بويىچە خاتىرە ئىزدەش", + "cloned_note_prefix_title": "كۆچۈرۈلگەن خاتىرىگە ئالدى قوشۇمچە بېرىلىدۇ", + "prefix_optional": "ئالدى قوشۇمچە (ئىختىيارىي)", + "clone_to_selected_note": "تاللانغان خاتىرىگە كۆچۈرۈش", + "no_path_to_clone_to": "كۆچۈرۈش يولى يوق.", + "note_cloned": "\"{{clonedTitle}}\" خاتىرە\"{{targetTitle}}\" غا كۆچۈرۈلدى" + }, + "confirm": { + "confirmation": "جەزملەشتۈرۈش", + "cancel": "بىكار قىلىش", + "ok": "تامام", + "are_you_sure_remove_note": "\"{{title}}\" ناملىق خاتىرىنى مۇناسىۋەت خەرىتىسىدىن ئۆچۈرۈۋېتىشنى جەزملەشتۈرەمسىز؟ ", + "if_you_dont_check": "ئەگەر بۇنى تاللىمىسىڭىز، خاتىرە پەقەت مۇناسىۋەت خەرىتىسىدىنلا ئۆچۈرۈلىدۇ (خاتىرىنىڭ ئۆزى ئۆچۈرۈلمەيدۇ).", + "also_delete_note": "خاتىرىنىمۇ ئۆچۈرۈۋېتىش" + }, + "delete_notes": { + "title": "خاتىرىلەرنى ئۆچۈرۈش", + "close": "ياپ", + "clones_label": "كۆچۈرۈلمىلەر", + "delete_clones_description_one": "يەنە {{count}} دانە باشقا كۆچۈرۈلمىنىمۇ ئۆچۈرىدۇ. يېقىنقى ئۆزگىرىشلەردىن ئەسلىگە كەلتۈرگىلى بولىدۇ.", + "delete_clones_description_other": "يەنە {{count}} دانە باشقا كۆچۈرۈلمىلەرنىمۇ ئۆچۈرىدۇ. يېقىنقى ئۆزگىرىشلەردىن ئەسلىگە كەلتۈرگىلى بولىدۇ.", + "delete_all_clones_description": "بارلىق كۆچۈرۈلمىلەرنىمۇ ئۆچۈرۈش (يېقىنقى ئۆزگىرىشلەردىن ئەسلىگە كەلتۈرگىلى بولىدۇ)", + "erase_notes_label": "مەڭگۈلۈك ئۆچۈرۈش", + "erase_notes_description": "يۇمشاق ئۆچۈرۈشنىڭ ئورنىغا خاتىرىنى دەرھال مەڭگۈلۈك ئۆچۈرىدۇ. بۇنى قايتۇرغىلى بولمايدۇ ۋە قوللىنىشنى قايتا يۈكلەيدۇ.", + "erase_notes_warning": "خاتىرىلەرنى مەڭگۈلۈك ئۆچۈرۈش (قايتۇرغىلى بولمايدۇ)، بارلىق كۆچۈرۈلمىلەرنىمۇ ئۆز ئىچىگە ئالىدۇ. بۇ قوللىنىشنى قايتا يۈكلەيدۇ.", + "notes_to_be_deleted": "ئۆچۈرۈلىدىغان خاتىرىلەر ({{notesCount}})", + "no_note_to_delete": "ھېچقانداق خاتىرە ئۆچۈرۈلمەيدۇ (پەقەت كۆچۈرۈلمىلەر).", + "broken_relations_to_be_deleted": "بۇزۇلغان مۇناسىۋەتلە ({{relationCount}})", + "table_note_with_relation": "مۇناسىۋەتلىك خاتىرە", + "table_relation": "مۇناسىۋەت", + "table_points_to": "ئورۇنغا ئۇلىنىدۇ(ئۆچۈرۈلگەن)", + "cancel": "بىكار قىلىش", + "delete": "ئۆچۈرۈش" + }, + "export": { + "export_note_title": "خاتىرىنى چىقىرىش", + "close": "يېپىش", + "export_type_subtree": "بۇ خاتىرە ۋە ئۇنىڭ بارلىق تارماقلىرى", + "format_html": "HTML - بارلىق فورماتلارنى ساقلاپ قالىدىغان بولغاچقا تەۋسىيە قىلىنىدۇ", + "format_html_zip": "ZIP ئارخىپىدىكى HTML - بارلىق پىچىملارنى ساقلاپ قالىدىغان بولغاچقا، بۇ تاللاش تەۋسىيە قىلىنىدۇ.", + "format_markdown": "Markdown - بۇ كۆپ قىسىم فورماتلارنى ساقلاپ قالىدۇ.", + "format_opml": "OPML - پەقەت تېكىست ئۈچۈن ئىشلىتىلىدىغان پىلان (outliner) ئالماشتۇرۇش فورماتى. بۇنىڭغا فورماتلار، رەسىملەر ۋە ھۆججەتلەر كىرمەيدۇ.", + "opml_version_1": "OPML v1.0 - پەقەت ساپ تېكىستلا", + "opml_version_2": "HTML OPMLv2 - نىمۇ قوللايدۇ", + "export_type_single": "تارماقلىرىنى ئۆز ئىچىگە ئالمايدۇ، پەقەت مۇشۇ خاتىرىنىڭ ئۆزىلا", + "export": "چىقىرىش", + "choose_export_type": "ئالدى بىلەن چىقىرىش تۈرىنى تاللاڭ", + "export_status": "چىقىرىش ھالىتى", + "export_in_progress": "چىقىرىلىۋاتىدۇ: {{progressCount}}", + "export_finished_successfully": "چىقىرىش مۇۋەپپەقىيەتلىك تاماملاندى.", + "format_pdf": "PDF - بېسىپ چىقىرىش ياكى ھەمبەھرلەش مەقسىتىدە ئىشلىتىلىدۇ.", + "share-format": "توردا ئېلان قىلىش ئۈچۈن HTML - ھەمبەھرلەنگەن خاتىرىلەر بىلەن ئوخشاش تېمىنى ئىشلىتىدۇ، لېكىن تۇراقلىق تور بېكەت سۈپىتىدە ئېلان قىلىشقا بولىدۇ." + }, + "help": { + "title": "ئاساسىي كۇنۇپكىلار", + "editShortcuts": "كۇنۇپكا تېزلەتمىلىرىنى تەھرىرلەش", + "noteNavigation": "خاتىرە يولباشچىسى", + "goUpDown": "خاتىرە تىزىملىكىدە يۇقىرى/تۆۋەنگە يۆتكىلىش", + "collapseExpand": "تۈرنى قاتلاش/يېيىش", + "notSet": "بېكىتىلمىگەن", + "goBackForwards": "تارىختا كەينىگە/ئالدىغا يۆتكىلىش", + "showJumpToNoteDialog": "", + "scrollToActiveNote": "نۆۋەتتىكى خاتىرىگە سىيرىپ بېرىش", + "jumpToParentNote": "تۈپ خاتىرىگە تېز يۆتكىلىش", + "collapseWholeTree": "بارلىق خاتىرە دەرىخىنى يىغىش", + "collapseSubTree": "تارماق تۈرلەرنى يىغىش", + "tabShortcuts": "بەتكۈچ تېزلەتمىلىرى", + "newTabNoteLink": "خاتىرە ئۇلىنىشىنى چەككەندە، خاتىرىنى يېڭى بەتكۈچتە ئېچىش", + "newTabWithActivationNoteLink": "خاتىرە ئۇلىنىشىنى يېڭى بەتكۈچتە ئېچىش ۋە ئاكتىپلاش", + "openEmptyTab": "بوش بەتكۈچ ئېچىش", + "creatingNotes": "خاتىرە قۇرۇش", + "createNoteAfter": "ئاكتىپ خاتىرىدىن كېيىن يېڭى خاتىرە قۇرۇش", + "createNoteInto": "ئاكتىپ خاتىرىنىڭ ئىچىگە يېڭى تارماق خاتىرە قۇرۇش", + "movingCloningNotes": "خاتىرە يۆتكەش / كۆچۈرۈش", + "moveNoteUpDown": "خاتىرىنى تىزىملىكتە ئاستىغا/ئۈستىگە يۆتكەش", + "moveNoteUpHierarchy": "خاتىرىنى دەرىجە بويىچە ئۈستىگە يۆتكەش", + "multiSelectNote": "ئۈستى/ئاستىدىكى خاتىرىلەرنى كۆپ تاللاش", + "selectAllNotes": "ھازىرقى دەرىجىدىكى بارلىق خاتىرىلەرنى تاللاش", + "selectNote": "خاتىرىنى تاللاش", + "cutNotes": "نۆۋەتتىكى خاتىرىنى (ياكى تاللانغانلارنى) كېسىپ چاپلاش تاختىسىغا ئېلىش (يۆتكەش ئۈچۈن ئىشلىتىلىدۇ)", + "pasteNotes": "خاتىرىنى ئاكتىپ خاتىرىنىڭ ئىچىگە تارماق خاتىرە قىلىپ چاپلاش", + "deleteNotes": "خاتىرە ياكى تارماق تۈرلەرنى ئۆچۈرۈش", + "editingNotes": "خاتىرىلەرنى تەھرىرلەش", + "createEditLink": "سىرتقى ئۇلانما قۇرۇش / تەھرىرلەش", + "createInternalLink": "ئىچكى ئۇلانما قۇرۇش", + "followLink": "نۇر بەلگىسى ئاستىدىكى ئۇلانمىغا ئەگىشىش", + "insertDateTime": "نۆۋەتتىكى ۋاقىت ۋە كۈننى قىستۇرۇش", + "jumpToTreePane": "خاتىرە تىزىملىكىگە ئۆتۈش ۋە ئاكتىپ خاتىرىنى كۆرسىتىش", + "markdownAutoformat": "Markdown شەكىللىك ئاپتوماتىك فورماتلاش", + "troubleshooting": "مەسىلە ھەل قىلىش", + "reloadFrontend": "Trilium نىڭ ئالدى يۈزىنى قايتا يۈكلەش", + "showDevTools": "تەتقىقاتچى قوراللىرىنى كۆرسىتىش", + "showSQLConsole": "SQL كونترول سۇپىسىنى كۆرسىتىش", + "other": "باشقىلار", + "quickSearch": "نۇر بەلگىسىنى تېز ئىزدەش رامكىسىغا يۆتكەش", + "inPageSearch": "بەت ئىچىدە ئىزدەش" + }, + "import": { + "importIntoNote": "خاتىرىگە كىرگۈزۈش", + "chooseImportFile": "كىرگۈزىدىغان ھۆججەتنى تاللاش", + "importDescription": "تاللانغان ھۆججەت مەزمۇنلىرى تارماق خاتىرە سۈپىتىدە كىرگۈزۈلىدىغان ئورۇن", + "importZipRecommendation": "ZIP ھۆججىتىنى كىرگۈزگەندە، ھۆججەت قىسقۇچلارنىڭ ئورۇنلاشتۇرۇلۇشىغا ئاساسەن ئاپتوماتىك تارماق خاتىرىلەر قۇرۇلىدۇ.", + "options": "تاللانمىلار", + "safeImport": "بىخەتەر كىرگۈزۈش", + "shrinkImages": "«رەسىملەرنى كىچىكلىتىش", + "textImportedAsText": "ئۇچۇرى ئېنىق بولمىغان HTML، Markdown ۋە TXT لارنى تېكىست خاتىرىسىگە ئايلاندۇرۇش", + "replaceUnderscoresWithSpaces": "ئەكىرىلگەن خاتىرە ناملىرىدىكى ئاستى سىزىقلارنى بوشلۇققا ئالماشتۇرۇش", + "import": "كىرگۈزۈش", + "failed": "ئەكىرىش مەغلۇپ بولدى: {{message}}.", + "html_import_tags": { + "title": "HTML ئەكىرىش بەلگىلىرى", + "description": "خاتىرىلەرنى ئەكىرگەندە قايسى HTML بەلگىلىرىنى ساقلاپ قېلىشنى سەپلەڭ. بۇ تىزىملىكتە بولمىغان بەلگىلەر ئەكىرىش جەريانىدا ئۆچۈرۈۋېتىلىدۇ. بەزى بەلگىلەر (مەسىلەن 'script') بىخەتەرلىك ئۈچۈن ھەمىشە ئۆچۈرۈۋېتىلىدۇ.", + "placeholder": "ھەر بىر قۇرغا بىر HTML بەلگىسىدىن كىرگۈزۈڭ", + "reset_button": "ئەسلىدىكى تىزىملىككە ئەسلىگە قايتۇرۇش" + }, + "import-status": "كىرگۈزۈش ھالىتى", + "in-progress": "ئەكىرىلىۋاتىدۇ: {{progress}}", + "successful": "كىرگۈزۈش مۇۋەپپەقىيەتلىك تاماملاندى." + }, + "include_note": { + "dialog_title": "خاتىرىنى ئۆز ئىچىگە ئېلىش", + "label_note": "خاتىرە", + "placeholder_search": "خاتىرىنى نامى بويىچە ئىزدەش" } } From c34df3a17a04ce4fb221ca4e0cfbfa88da5629b7 Mon Sep 17 00:00:00 2001 From: seb2020 Date: Wed, 15 Apr 2026 07:17:49 +0200 Subject: [PATCH 179/203] Translated using Weblate (French) Currently translated at 88.8% (1671 of 1880 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/fr/ --- apps/client/src/translations/fr/translation.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/fr/translation.json b/apps/client/src/translations/fr/translation.json index 5e0ef3a6c8..de1a84f60b 100644 --- a/apps/client/src/translations/fr/translation.json +++ b/apps/client/src/translations/fr/translation.json @@ -95,7 +95,10 @@ "notes_to_be_deleted": "Les notes suivantes seront supprimées ({{notesCount}})", "no_note_to_delete": "Aucune note ne sera supprimée (uniquement les clones).", "broken_relations_to_be_deleted": "Les relations suivantes seront rompues et supprimées ({{ relationCount}})", - "cancel": "Annuler" + "cancel": "Annuler", + "title": "Notes supprimées", + "clones_label": "Clone", + "erase_notes_label": "Supprimer définitivement" }, "export": { "export_note_title": "Exporter la note", @@ -1176,7 +1179,7 @@ }, "attachment_erasure_timeout": { "attachment_erasure_timeout": "Délai d'effacement des pièces jointes", - "erase_attachments_after": "Effacer les pièces jointes inutilisées après :", + "erase_attachments_after": "Effacer les pièces jointes inutilisées après :", "manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte du délai défini ci-dessus) :", "erase_unused_attachments_now": "Effacez maintenant les pièces jointes inutilisées", "unused_attachments_erased": "Les pièces jointes inutilisées ont été effacées." From e50ef8552b7137dbf9e045061561ee9a22d97ba9 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 15 Apr 2026 09:29:37 +0200 Subject: [PATCH 180/203] Update translation files Updated by "Remove blank strings" add-on in Weblate. Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ --- apps/client/src/translations/ug/translation.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/src/translations/ug/translation.json b/apps/client/src/translations/ug/translation.json index 7d3a4d0e32..e0b866dd10 100644 --- a/apps/client/src/translations/ug/translation.json +++ b/apps/client/src/translations/ug/translation.json @@ -133,7 +133,6 @@ "collapseExpand": "تۈرنى قاتلاش/يېيىش", "notSet": "بېكىتىلمىگەن", "goBackForwards": "تارىختا كەينىگە/ئالدىغا يۆتكىلىش", - "showJumpToNoteDialog": "", "scrollToActiveNote": "نۆۋەتتىكى خاتىرىگە سىيرىپ بېرىش", "jumpToParentNote": "تۈپ خاتىرىگە تېز يۆتكىلىش", "collapseWholeTree": "بارلىق خاتىرە دەرىخىنى يىغىش", From a32eeb27e363a6323173a48441335c3531049563 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Wed, 15 Apr 2026 14:38:36 +0200 Subject: [PATCH 181/203] Translated using Weblate (Uyghur) Currently translated at 87.9% (139 of 158 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/ug/ --- .../src/translations/ug/translation.json | 182 +++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/apps/website/src/translations/ug/translation.json b/apps/website/src/translations/ug/translation.json index a7fb8b96c0..6f7382faba 100644 --- a/apps/website/src/translations/ug/translation.json +++ b/apps/website/src/translations/ug/translation.json @@ -2,10 +2,188 @@ "get-started": { "title": "باشلاش", "architecture": "قۇرۇلما:", - "older_releases": "كونا نۇسخىلىرىنى كۆرۈش" + "older_releases": "كونا نۇسخىلىرىنى كۆرۈش", + "desktop_title": "ئېكران نۇسخىسىنى چۈشۈرۈش (v{{version}})", + "server_title": "كۆپ خىل ئۈسكۈنىلەردە ئىشلىتىش ئۈچۈن مۇلازىمېتىر تەڭشەش" }, "hero_section": { "title": "ئويلىرىڭىزنى رەتلەڭ. شەخسىي بىلىم ئامبىرىڭىزنى قۇرۇپ چىقىڭ.", - "subtitle": "Trilium — خاتىرە قالدۇرۇش ۋە شەخسىي بىلىم ئامبىرىنى رەتلەش ئۈچۈن لايىھەلەنگەن ئوچۇق كودلۇق ھەل قىلىش چارىسىدۇر. ئۇنى ئۆزىڭىزنىڭ ئۈستەلئۈستى كومپيۇتېرىدا بىۋاسىتە ئىشلىتەلەيسىز ياكى ئۆزىڭىز قۇرغان (self-hosted) مۇلازىمېتىر بىلەن ماسقەدەملەپ، خاتىرىلىرىڭىزنى ھەر ۋاقىت، ھەر قانداق يەردە زىيارەت قىلالايسىز." + "subtitle": "Trilium — خاتىرە قالدۇرۇش ۋە شەخسىي بىلىم ئامبىرىنى رەتلەش ئۈچۈن لايىھەلەنگەن ئوچۇق كودلۇق ھەل قىلىش چارىسىدۇر. ئۇنى ئۆزىڭىزنىڭ ئۈستەلئۈستى كومپيۇتېرىدا بىۋاسىتە ئىشلىتەلەيسىز ياكى ئۆزىڭىز قۇرغان (self-hosted) مۇلازىمېتىر بىلەن ماسقەدەملەپ، خاتىرىلىرىڭىزنى ھەر ۋاقىت، ھەر قانداق يەردە زىيارەت قىلالايسىز.", + "get_started": "باشلاش", + "github": "GitHub", + "dockerhub": "Docker Hub", + "screenshot_alt": "Trilium Notes ئېكران نۇسخىسىنىڭ ئېكران سۈرىتى" + }, + "organization_benefits": { + "title": "تەشكىلات", + "note_structure_title": "خاتىرە قۇرۇلمىسى", + "note_structure_description": "خاتىرىلەرنى دەرىجە (قاتلام) بويىچە تىزىشقا بولىدۇ. ھەر بىر خاتىرە ئۆز ئىچىگە تارماق خاتىرىلەرنى ئالالايدىغان بولغاچقا، قىسقۇچ (folder) ئىشلىتىشنىڭ ھاجىتى يوق. بىرلا خاتىرىنى قاتلاملىق قۇرۇلمىنىڭ كۆپلىگەن ئورۇنلىرىغا قوشقىلى بولىدۇ.", + "attributes_title": "خاتىرە بەلگىلىرى ۋە مۇناسىۋەتلىرى", + "attributes_description": "خاتىرىلەر ئارىسىدىكى مۇناسىۋەت ياكى بەلگىلەر ئارقىلىق تۈرگە ئايرىشنى قولايلاشتۇرۇڭ. يۇقىرى كۆتۈرۈلگەن خاسلىقلار (Promoted attributes) ئارقىلىق قۇرۇلمىلىق ئۇچۇرلارنى كىرگۈزۈڭ، بۇ ئۇچۇرلارنى جەدۋەل ۋە كانبان (Kanban) لاردا ئىشلەتكىلى بولىدۇ.", + "hoisting_title": "خىزمەت رايونى ۋە يۇقىرى كۆتۈرۈش", + "hoisting_description": "شەخسىي ۋە خىزمەت خاتىرىلىرىڭىزنى ئوخشىمىغان خىزمەت رايونلىرىغا گۇرۇپپىلاش ئارقىلىق، ئۇلارنى ئاسانلا بىر-بىرىدىن ئايرىۋەتەلەيسىز. ھەر بىر خىزمەت رايونى مەلۇم بىر خاتىرە توپلىمىغا مەركەزلەشكەن بولۇپ، خاتىرە دەرىخىڭىزدە پەقەت شۇنىڭغا مۇناسىۋەتلىك مەزمۇنلارنىلا كۆرسىتىدۇ." + }, + "productivity_benefits": { + "title": "يۇقىرى ئۈنۈملۈك ۋە بىخەتەر", + "revisions_title": "خاتىرە نۇسخا تارىخى", + "revisions_content": "خاتىرىلەر ئارقا سىپتا قەرەللىك ساقلىنىپ تۇرىدۇ، تۈزىتىلگەن نۇسخىلىرى ئارقىلىق بۇرۇنقى مەزمۇنلارنى كۆرگىلى ياكى خاتالاشقان ئۆزگەرتىشلەرنى ئەسلىگە كەلتۈرگىلى بولىدۇ. سىز يەنە ئېھتىياجغا قاراپ ئۆزىڭىز تۈزىتىلگەن نۇسخا قۇرالايسىز.", + "sync_title": "ماسقەدەملەش", + "sync_content": "ئۆزىڭىز قۇرغان ياكى بۇلۇتتىكى مۇلازىمېتىر ئارقىلىق، خاتىرىلىرىڭىزنى كۆپ ئۈسكۈنىلەر ئارىسىدا ئاسانلا ماسقەدەملىيەلەيسىز ھەمدە PWA ئارقىلىق تېلېفونىڭىزدا زىيارەت قىلالايسىز.", + "protected_notes_title": "قوغدالغان خاتىرىلەر", + "protected_notes_content": "سېزىمچان شەخسىي ئۇچۇرلىرىڭىزنى قوغداش ئۈچۈن، خاتىرىنى شىفىرلاڭ ھەمدە ئۇنى پارول بىلەن قوغدىلىدىغان مەخسۇس مەشغۇلات باسقۇچى (session) ئىچىگە قۇلۇپلاڭ.", + "jump_to_title": "تېز ئىزدەش ۋە بۇيرۇقلار", + "jump_to_content": "ماۋزۇ ئارقىلىق ئىزدەش ئارقىلىق، قاتلاملىق قۇرۇلمىدىكى خاتىرىلەرگە ياكى UI بۇيرۇقلىرىغا تېز سۈرئەتتە سەكرىگىلى بولىدۇ؛ تۇتۇق ماسلاشتۇرۇش (fuzzy matching) ئىقتىدارى ئىملا خاتالىقى ياكى ئازراق پەرقنى قوبۇل قىلالايدۇ.", + "search_title": "كۈچلۈك ئىزدەش ئىقتىدارى", + "search_content": "ياكى خاتىرىلەردىن تېكىست ئىزدەڭ ھەمدە تۈپ خاتىرە ياكى ئىزدەش چوڭقۇرلۇقىنى سۈزۈش ئارقىلىق ئىزدەش دائىرىسىنى كىچىكلىتىڭ.", + "web_clipper_title": "تور بەت تۇتۇپ ئېلىش", + "web_clipper_content": "تور بەت مەزمۇنىنى تۇتۇپ ئېلىڭ (ياكى ئېكران رەسىمىنى تۇتۇڭ) ھەمدە تور بەت كېسىپ ئالغۇچ (Web Clipper) قىستۇرمىسى ئارقىلىق بىۋاسىتە Trilium غا ساقلاڭ." + }, + "extensibility_benefits": { + "import_export_description": "Markdown، ENEX ۋە OPML فورماتلىرىدىن پايدىلىنىپ، باشقا ئەپ پروگراممىلىرى بىلەن ئاسانلا سانلىق مەلۇمات ئالماشتۇرغىلى بولىدۇ.", + "share_title": "خاتىرىلەرنى توردا ھەمبەھرىلەش", + "share_description": "ئەگەر سىزدە بىر مۇلازىمەت ماشىنىسى بولسا، ئۇنىڭدىن پايدىلىنىپ خاتىرىلىرىڭىزنىڭ بىر قىسمىنى باشقىلار بىلەن ھەمبەھرىلىيەلەيسىز.", + "scripting_title": "يۇقىرى دەرىجىلىك كود يېزىش", + "scripting_description": "Trilium دا ئۆزىڭىز ياسىغان كىچىك قوراللار ياكى Server تەرەپتىكى لوگىكا ئارقىلىق، ئۆزىڭىزگە خاس بىر گەۋدىلەشكەن ھەل قىلىش لايىھەسىنى قۇرۇپ چىقىڭ.", + "api_title": "REST API", + "api_description": "ئىچىگە ئورۇنلاشتۇرۇلغان REST API ئارقىلىق، پروگرامما تۈزۈش (كود يېزىش) ئۇسۇلىدا Trilium بىلەن سانلىق مەلۇمات ئالماشتۇرۇڭ.", + "title": "ھەمبەھىرلىنىش ۋە كېڭەيتىشچانلىقى", + "import_export_title": "كىرگۈزۈش / چىقىرىش" + }, + "collections": { + "title": "توپلىمىلار", + "calendar_title": "كالىندار", + "calendar_description": "شەخسىي ياكى خىزمەت پائالىيەتلىرىڭىزنى كالىندار ئارقىلىق باشقۇرۇڭ؛ پۈتۈن كۈنلۈك ۋە كۆپ كۈنلۈك پائالىيەتلىرىڭىزنى قوللايدۇ. ھەپتىلىك، ئايلىق ۋە يىللىق كۆرۈنۈشلەر ئارقىلىق بارلىق پائالىيەتلەرنى تېز سۈرئەتتە كۆرەلەيسىز. ئاددىي مەشغۇلات ئارقىلىقلا پائالىيەت قوشالايسىز ياكى سۆرەپ ئورنىنى ئۆزگەرتەلەيسىز.", + "table_title": "جەدۋەل", + "table_description": "خاتىرە ئۇچۇرلىرىنى جەدۋەل قۇرۇلمىسىدا كۆرسىتىڭ ۋە تەھرىرلەڭ؛ تېكىست، سان، تاللاش كۆزنەكچىسى (checkbox)،چېسلا ۋە ۋاقىت، ئۇلىنىش ھەمدە رەڭ قاتارلىق كۆپ خىل ئىستون تىپلىرىنى، شۇنداقلا مۇناسىۋەت (relation) ئىقتىدارىنى قوللايدۇ. ئېھتىياجغا قاراپ، جەدۋەل ئىچىدە خاتىرىلەرنى دەرەخسىمان قاتلاملىق قۇرۇلمىدا كۆرسىتىشكە بولىدۇ.", + "board_title": "كانبان تاختىسى", + "board_description": "ۋەزىپە ياكى تۈر ھالىتىنى كانبان تاختىسى شەكلىدە رەتلەڭ؛ يېڭى تۈرلەرنى ۋە ستونلارنى ئاسانلا قۇرۇڭ، كارتىلارنى سۆرىەپلا ئۇلارنىڭ بېجىرىلىش ھالىتىنى ئاددىي ئۇسۇلدا ئۆزگەرتىڭ.", + "geomap_title": "جۇغراپىيەلىك خەرىتە", + "geomap_description": "ئىختىيارىي بەلگىلەر ئارقىلىق، جۇغراپىيەلىك خەرىتە ئۈستىدە تەتىل پىلانىڭىزنى تۈزۈڭ ياكى قىزىقىش نۇقتىلىرىنى بەلگىلەڭ. خاتىرىلەنگەن GPX يولىنى كۆرسىتىش ئارقىلىق، سەپەر لىنىيەسىنى ئىز قوغلاپ كۆزەتكىلى بولىدۇ.", + "presentation_title": "تەقدىمات", + "presentation_description": "ئۇچۇرلارنى سلايد شەكلىدە رەتلەڭ ھەمدە راۋان ئۆتۈش ئۈنۈملىرى بىلەن پۈتۈن ئېكران ئەندىزىسىدە نامايان قىلىڭ. سلايدلارنى يەنە PDF شەكلىدە چىقىرىپ، باشقىلار بىلەن ئاسانلا ھەمبەھىرلىنىشكە بولىدۇ." + }, + "note_types": { + "title": "ئۇچۇرلارنى كۆرسىتىشنىڭ كۆپ خىل ئۇسۇللىرى", + "text_title": "تېكىستلىك خاتىرە", + "text_description": "خاتىرىلەرنى كۆرۈنۈشلۈك (ئېكراندا نېمىنى كۆرسىڭىز شۇ چىقىدىغان - WYSIWYG) تەھرىرلىگۈچ ئارقىلىق تەھرىرلىگىلى بولىدۇ؛ جەدۋەل، رەسىم، ماتېماتىكىلىق ئىپادىلەر ۋە كود نۇرى يورۇتۇلغان (syntax highlighting) كود بۆلەكلىرىنى قوللايدۇ. سىز Markdown غا ئوخشاش گرامماتىكا ياكى قىيپاش سىزىق (/) بۇيرۇقى ئارقىلىق تېكىست پىچىمىنى تېز سۈرئەتتە تەڭشىيەلەيسىز.", + "code_title": "كود خاتىرىسى", + "code_description": "كۆپ مىقداردىكى ئەسلى كودلار ياكى سىناق تېكىستلەر (scripts) ئادەتتە مەخسۇس تەھرىرلىگۈچتە يېزىلىدۇ؛ بۇ خىل تەھرىرلىگۈچلەر كۆپ خىل پروگرامما تىللىرىنى گرامماتىكىلىق نۇر بېرىش (syntax highlighting) بىلەن تەمىنلەيدۇ ھەمدە كۆپ خىل رەڭ تېمىلىرىنى قوللايدۇ.", + "file_title": "ھۆججەتلىك خاتىرە", + "file_description": "كۆپ ۋاسىتىلىك ھۆججەتلەرنى (PDF، رەسىم، سىن دېگەندەك) سىڭدۈرۈڭ ھەمدە ئەپ ئىچىدىلا ئالدىن كۆرۈڭ.", + "canvas_title": "سىزمىچىلىق تاختىسى", + "canvas_description": "چەكسىز سىزمىچىلىق تاختىسىدا شەكىللەرنى، رەسىملەرنى ۋە تېكىستلەرنى ئەركىن ئورۇنلاشتۇرۇڭ؛ ئۇ excalidraw.com بىلەن ئوخشاش تېخنىكىنى قوللىنىدۇ. ئۇ دىئاگرامما سىزىش، نۇسخا سىزما (draft) تەييارلاش ۋە كۆرۈنۈشلۈك پىلان تۈزۈش ئۈچۈن ئىنتايىن ماس كېلىدۇ.", + "mermaid_title": "Mermaid دىئاگراممىسى", + "mermaid_description": "Mermaid گرامماتىكىسىدىن پايدىلىنىپ جەريان خەرىتىسى، تۈر (class) دىئاگراممىسى، تەرتىپ دىئاگراممىسى ۋە گانت جەدۋىلى قاتارلىق تۈرلۈك دىئاگراممىلارنى قۇرۇڭ.", + "mindmap_title": "تەپەككۇر خەرىتىسى", + "mindmap_description": "ئوي-خىياللىرىڭىزنى كۆرۈنۈشلۈك ئۇسۇلدا رەتلەڭ ياكى بىر قېتىملىق پىكىر قوزغىتىش (brainstorming) ئېلىپ بېرىڭ.", + "others_list": "ۋە باشقىلار: <0>خاتىرىلەر خەرىتىسى، <1>مۇناسىۋەتلەر خەرىتىسى، <2>ساقلانغان ئىزدەشلەر، <3>خاتىرىنى رەڭلەش ۋە <4>تور كۆرۈنۈشلىرى." + }, + "faq": { + "title": "دائىم سورىلىدىغان سوئاللار", + "mobile_question": "كۆچمە ئەپ بارمۇ؟", + "mobile_answer": "ھازىرچە رەسمىي كۆچمە ئەپ يوق. ئەمما سىزدە مۇلازىمەت ماشىنىسى مىسالى (Server Instance) بولسا، تور كۆرگۈچ ئارقىلىق زىيارەت قىلالايسىز، ھەتتا ئۇنى ئىلگىرىلىگەن تور ئەپى (PWA) سۈپىتىدە قاچىلىۋالالايسىز. ئاندروئىد ئىشلەتكۈچىلىرى بولسا TriliumDroid ناملىق غەيرىي رەسمىي ئەپنى ئىشلەتسە بولىدۇ، بۇ ئەپ تورسىز ئىشلىتىشنى قوللايدۇ (ئىقتىدارى كومپيۇتېر نۇسخىسى بىلەن ئوخشاش).", + "database_question": "سانلىق مەلۇماتلار نەگە ساقلىنىدۇ؟", + "database_answer": "بارلىق خاتىرىلەر ئەپ قىسقۇچى ئىچىدىكى SQLite ساندانىدا ساقلىنىدۇ. Trilium نىڭ ساپ تېكىستلىك ھۆججەت ئەمەس، بەلكى ساندان ئىشلىتىشىدىكى سەۋەب: ساندان ئىقتىدارنى يۇقىرى كۆتۈرىدۇ، شۇنداقلا بەزى ئىقتىدارلارنى (مەسىلەن، كىلونلاش ئىقتىدارى — يەنى بىر خاتىرىنى دەرەخسىمان قۇرۇلمىنىڭ كۆپ يېرىدە كۆرسىتىش) تېكىستلىك ھۆججەت ئارقىلىق ئىشقا ئاشۇرۇش بىر قەدەر قىيىن. ئەپ قىسقۇچىنى تېپىش ئۈچۈن، <ھەققىدە> (About) كۆزنىكىگە كىرسىڭىزلا بولىدۇ.", + "server_question": "Trilium نى ئىشلىتىش ئۈچۈن مۇلازىمەت ماشىنىسى لازىممۇ؟", + "server_answer": "ياق، مۇلازىمېتىر تور كۆرگۈچ ئارقىلىق زىيارەت قىلىشنى قوللايدۇ ھەمدە سىزدە كۆپ ئۈسكۈنىلەر بولغاندا ماسقەدەملەشنى باشقۇرىدۇ. ئەگەر ماسقەدەملەش ئېھتىياجىڭىز بولمىسا، پەقەت كومپيۇتېر نۇسخىسىنى (Desktop App) چۈشۈرۈپ ئىشلەتسىڭىزلا كاپايە.", + "scaling_question": "بۇ ئەپ نۇرغۇن مىقداردىكى خاتىرىلەرنى بىر تەرەپ قىلىشتا قانچىلىك كېڭەيمىچىلىككە ئىگە؟", + "scaling_answer": "ئىشلىتىش ئەھۋالىغا ئاساسلانغاندا، بۇ ئەپ ئاز دېگەندىمۇ 100 مىڭ خاتىرىنى ھېچقانداق توسالغۇسىز بىر تەرەپ قىلالايدۇ. ئەمما شۇنىڭغا دىققەت قىلىڭكى، ئەگەر سىز ناھايىتى كۆپ مىقداردىكى چوڭ ھۆججەتلەرنى (مەسىلەن، بىر دانە ھۆججەتنىڭ چوڭلۇقى 1 GB غا يەتسە) يۈكلىسىڭىز، ماسقەدەملەش جەريانى مەغلۇپ بولۇشى مۇمكىن. چۈنكى Trilium ھۆججەت ساقلاش (مەسىلەن: NextCloud) ئەمەس، بەلكى بىلىم ئامبىرى قورالى بولۇشقا بەكرەك ئەھمىيەت بېرىدۇ.", + "network_share_question": "ساندانىمنى تور دېسكىسى (Cloud Drive) ئارقىلىق ھەمبەھىرلىيەلەمدىم؟", + "network_share_answer": "ياق، ئادەتتە SQLite ساندانىنى تور دېسكىسى ئارقىلىق ھەمبەھىرلەش تەۋسىيە قىلىنمايدۇ. گەرچە بەزىدە ئۈنۈم بەرگەندەك قىلسىمۇ، لېكىن تور مۇھىتىدىكى ھۆججەت قۇلۇپلاش مېخانىزمىنىڭ مۇكەممەل بولماسلىقى سەۋەبىدىن، سانداننىڭ بۇزۇلۇپ كېتىش خەۋپى بار.", + "security_question": "سانلىق مەلۇماتلىرىم قانداق قوغدىلىدۇ؟", + "security_answer": "سۈكۈتتىكى ھالەتتە، خاتىرىلەر شىفىرلانمايدۇ، سانداندىن بىۋاسىتە ئوقۇشقا بولىدۇ. ئەگەر خاتىرە شىفىرلانغان ھالەت دەپ بەلگىلەنسە، ئۇنداقتا شۇ خاتىرە AES-128-CBC ئالگورىزىمى بىلەن شىفىرلىنىدۇ." + }, + "final_cta": { + "title": "Trilium Notes نى ئىشلەتكىلى تەييار بولدىڭىزمۇ؟", + "description": "كۈچلۈك ئىقتىدار ۋە تولۇق شەخسىيەتنى قوغداشقا تايىنىپ، ئۆزىڭىزنىڭ شەخسىي بىلىم ئامبىرىنى قۇرۇپ چىقىڭ.", + "get_started": "ئىشلىتىشنى باشلاش" + }, + "components": { + "link_learn_more": "تەپسىلاتىنى كۆرۈش..." + }, + "download_now": { + "text": "ھازىر چۈشۈرۈش ", + "platform_big": "{{platform}} ئۈچۈن v{{version}}", + "platform_small": "{{platform}} ئۈچۈن", + "linux_big": "Linux ئۈچۈن v{{version}}", + "linux_small": "Linux ئۈچۈن", + "more_platforms": "باشقا سۇپىلار ۋە مۇلازىمېتىر تەڭشەكلىرى" + }, + "header": { + "get-started": "ئىشلىتىشنى باشلاش", + "documentation": "ھۆججەتلەر", + "resources": "مەنبەلەر", + "support-us": "بىزنى قوللاڭ" + }, + "footer": { + "copyright_and_the": " ھەمدە ", + "copyright_community": "جامائەت" + }, + "social_buttons": { + "github": "GitHub", + "github_discussions": "GitHub مۇنازىرىسى", + "matrix": "Matrix", + "reddit": "Reddit" + }, + "support_us": { + "title": "بىزنى قوللاڭ", + "financial_donations_title": "ئىقتىسادىي ياردەم", + "financial_donations_description": "Trilium نى ئېچىش ۋە قوغداشقا يۈزلىگەن سائەتلىك خىزمەت سىڭدۈرۈلدى. سىزنىڭ قوللىشىڭىز ئۇنىڭ ئوچۇق مەنبەلىك خاراكتېرىنىڭ داۋاملىشىشىغا كاپالەتلىك قىلىدۇ، ئىقتىدارلىرىنىڭ ئۈزلۈكسىز ئەلالىشىشىنى ئىلگىرى سۈرىدۇ ھەمدە باشقۇرۇش (Hosting) قاتارلىق تىجارەت تەننەرخلىرىنى قامدايدۇ.", + "financial_donations_cta": "تۆۋەندىكى ئۇسۇللار ئارقىلىق بۇ ئەپنىڭ ئاساسلىق ئاچقۇچىسىنى (eliandoran) قوللاشنى ئويلىشىپ كۆرۈڭ:", + "github_sponsors": "GitHub Sponsors", + "paypal": "PayPal", + "buy_me_a_coffee": "Buy Me A Coffee" + }, + "contribute": { + "title": "تۆھپە قوشۇشنىڭ باشقا ئۇسۇللىرى", + "way_translate": "Weblate ئارقىلىق بۇ ئەپنى ئۆز ئانا تىلىڭىزغا تەرجىمە قىلىڭ.", + "way_community": "GitHub مۇنازىرىسى ياكى Matrix سۇپىسىدا جامائەت بىلەن ئۆزئارا ئالاقە قىلىڭ.", + "way_reports": "GitHub issues ئارقىلىق خاتالىق مەلۇم قىلىڭ.", + "way_document": "تۆھپە قوشۇش قوللانمىسى، كۆپ كۆرۈلىدىغان سوئاللارغا جاۋاب ياكى دەرسلىكلەرنى تەمىنلەش، شۇنداقلا قوللانمىدىكى كەم قالغان مەزمۇنلارنى مەلۇم قىلىش ئارقىلىق قوللانمىنى مۇكەممەللەشتۈرۈڭ.", + "way_market": "كەڭ تەرغىب قىلىڭ: Trilium Notes نى دوستلىرىڭىزغا تەۋسىيە قىلىڭ ياكى بىلوگ ۋە ئىجتىمائىي تاراتقۇلاردا ھەمبەھىرلىنىڭ." + }, + "404": { + "title": "404: تېپىلمىدى", + "description": "سىز ئىزدىگەن بەت تېپىلمىدى. بۇ بەت ئۆچۈرۈلگەن بولۇشى ياكى تور ئادرېسىدا خاتالىق بولۇشى مۇمكىن." + }, + "download_helper_desktop_windows": { + "title_x64": "Windows 64-bit", + "title_arm64": "Windows on ARM", + "description_x64": "Windows 10 ۋە 11 ئىجرا قىلىنىدىغان Intel ياكى AMD ئۈسكۈنىلىرىگە ماس كېلىدۇ.", + "description_arm64": "ARM ئۈسكۈنىلىرىگە ماس كېلىدۇ (مەسىلەنQualcomm Snapdragonبىر تەرەپ قىلغۇچ سەپلەنگەن ئۈسكۈنىلەر).", + "quick_start": "Winget ئارقىلىق قاچىلاش:", + "download_exe": "قاچىلاش پروگراممىسىنى چۈشۈرۈش (.exe)", + "download_zip": "Portable (.zip)", + "download_scoop": "Scoop" + }, + "download_helper_desktop_linux": { + "title_x64": "Linux 64-bit", + "title_arm64": "Linux on ARM", + "description_x64": "كۆپىنچە Linux تارقاتما نۇسخىلىرىغا ماس كېلىدۇ، x86_64 ئارخىتېكتۇرىسى بىلەن ماسلىشىشچانلىقى بار.", + "description_arm64": "ARM ئارخىتېكتۇرىسىنى ئاساس قىلغان Linux تارقاتما نۇسخىلىرىغا ماس كېلىدۇ، aarch64 ئارخىتېكتۇرىسى بىلەن ماسلىشىشچانلىقى بار.", + "quick_start": "ئۆزىڭىز ئىشلىتىۋاتقان تارقاتما نۇسخاغا ئاساسەن مۇۋاپىق بولغان يۇمشاق دېتال فورماتىنى تاللاڭ:", + "download_deb": ".deb" + }, + "download_helper_desktop_macos": { + "description_x64": "macOS Monterey ياكى ئۇنىڭدىن كېيىنكى سىستېما ئىجرا قىلىنىدىغان Intel يادرولۇق Mac كومپيۇتېرلىرىغا ماس كېلىدۇ.", + "description_arm64": "M1 ۋە M2 ئۆزەكلىرى سەپلەنگەن Apple Silicon Mac كومپيۇتېرلىرىغا ماس كېلىدۇ.", + "quick_start": "Homebrew ئارقىلىق قاچىلاش:", + "download_dmg": "قاچىلاش پروگراممىسىنى چۈشۈرۈش (.dmg)" + }, + "download_helper_server_docker": { + "title": "Docker ئارقىلىق ئۆزىڭىز قۇرۇپ ئىشلىتىش (Self-hosted)", + "description": "Docker كونتېينېرى (Container) ئارقىلىق Windows، Linux ياكى macOS سىستېمىلىرىغا ئاسانلا ئورۇنلاشتۇرۇشقا بولىدۇ." + }, + "download_helper_server_linux": { + "title": "Linuxدا ئۆزىڭىز قۇرۇپ ئىشلىتىش (Self-hosted)", + "description": "Trilium Notes نى ئۆزىڭىزنىڭ مۇلازىمەتچىسىگە ياكى VPS غا ئورۇنلاشتۇرۇڭ، كۆپىنچە تارقاتما نۇسخىلار بىلەن ماسلىشىشچانلىقى بار." + }, + "download_helper_server_hosted": { + "description": "Trilium Notes نىڭ بېكەت تۇرالغۇسى PikaPods تەرىپىدىن تەمىنلىنىدۇ، بۇ ھەقلىق مۇلازىمەت قولايلىق زىيارەت ۋە باشقۇرۇش ئىقتىدارلىرىغا ئىگە. مەزكۇر مۇلازىمەت بىلەن Trilium گۇرۇپپىسى ئوتتۇرىسىدا بىۋاسىتە باغلىنىش يوق.", + "download_pikapod": "PikaPods تا تەڭشەش", + "download_triliumcc": "يەنە trilium.cc غا كىرسىڭىزمۇ بولىدۇ" + }, + "resources": { + "title": "مەنبەلەر" } } From 0ece1270c7a04e9ee6dca6a0e0dfa75c7442cd7f Mon Sep 17 00:00:00 2001 From: green Date: Wed, 15 Apr 2026 14:12:42 +0200 Subject: [PATCH 182/203] Translated using Weblate (Japanese) Currently translated at 99.9% (1919 of 1920 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- .../src/translations/ja/translation.json | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index c310f1e846..5884ece190 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -1202,7 +1202,9 @@ "and_more": "...その他 {{count}} 件。", "print_landscape": "PDF にエクスポートするときに、ページの向きを縦向きではなく横向きに変更します。", "print_page_size": "PDF にエクスポートするときに、ページのサイズを変更します。サポートされる値: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger。", - "textarea": "複数行テキスト" + "textarea": "複数行テキスト", + "print_scale": "PDF にエクスポートする際に、レンダリングされるコンテンツの縮尺を変更します。値の範囲は 0.1(10%)から 2(200%)までで、デフォルト値は 1(100%)です。", + "print_margins": "PDF に書き出す際に、ページ余白を設定します。デフォルト, なし, 最小, または 上、右、下、左 のようなカスタム値をミリメートル単位で指定できます。" }, "link_context_menu": { "open_note_in_popup": "クイック編集", @@ -1354,7 +1356,9 @@ "theme_group_light": "ライトテーマ", "theme_group_dark": "ダークテーマ", "copy_title": "クリップボードにコピー", - "click_to_copy": "クリックしてコピー" + "click_to_copy": "クリックしてコピー", + "tab_width": "タブ幅", + "tab_width_unit": "スペース" }, "editor": { "title": "エディター" @@ -1607,7 +1611,9 @@ "check_integrity_description": "SQLite レベルでデータベースが破損していないことを確認します。" }, "code-editor-options": { - "title": "エディター" + "title": "エディター", + "tab_width": "タブ幅", + "tab_width_unit": "スペース" }, "search_string": { "title_column": "文字列検索:", @@ -2130,7 +2136,19 @@ "code_note_switcher": "言語モードを変更", "backlinks_other": "{{count}} バックリンク", "attachments_other": "{{count}} 件の添付ファイル", - "note_paths_other": "{{count}} 個のパス" + "note_paths_other": "{{count}} 個のパス", + "tab_width": "タブ幅: {{width}}", + "tab_width_title": "タブ幅を変更", + "tab_width_spaces": "{{count}} スペース", + "tab_width_spaces_short": "スペース: {{width}}", + "tab_width_tabs": "タブ ({{width}})", + "tab_width_use_default": "デフォルトを使用 ({{width}})", + "tab_width_use_default_style": "デフォルトを使用 ({{style}})", + "tab_width_display_header": "表示幅", + "tab_width_reindent_header": "コンテンツのインデントを再設定", + "tab_width_style_header": "インデントを使用", + "tab_width_style_spaces": "スペース", + "tab_width_style_tabs": "タブ" }, "breadcrumb": { "hoisted_badge": "ホイスト", @@ -2395,5 +2413,29 @@ }, "link": { "failed_to_open": "'{{- href}}' のリンクを開けなせんでした: {{- message}}" + }, + "print_preview": { + "title": "印刷プレビュー", + "close": "閉じる", + "save": "PDF として保存", + "orientation": "向き", + "portrait": "縦向き", + "landscape": "横向き", + "page_size": "ページサイズ", + "scale": "縮尺", + "margins": "余白", + "render_error": "現在の設定では PDF を生成できません。余白と縮尺を確認してください。", + "margins_default": "デフォルト", + "margins_none": "なし", + "margins_minimum": "最小", + "margins_custom": "カスタム", + "margin_top": "上", + "margin_right": "右", + "margin_bottom": "下", + "margin_left": "左", + "page_ranges": "ページ", + "page_ranges_hint": "全ページを印刷する場合は空欄のままにしてください。", + "page_ranges_invalid": "無効な形式です。例: 1-5, 8, 11-13 のように指定してください。", + "page_ranges_placeholder": "例: 1-5, 8, 11-13" } } From 60f3ddd35444827252494880a2039af123fd3dfa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:48:53 +0000 Subject: [PATCH 183/203] Update dependency @ai-sdk/google to v3.0.62 --- apps/server/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index 766b6234d2..3410dd7ee5 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@ai-sdk/anthropic": "3.0.69", - "@ai-sdk/google": "3.0.61", + "@ai-sdk/google": "3.0.62", "@ai-sdk/openai": "3.0.52", "@modelcontextprotocol/sdk": "^1.12.1", "ai": "6.0.158", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37746a1459..a61dde5251 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -572,8 +572,8 @@ importers: specifier: 3.0.69 version: 3.0.69(zod@4.3.6) '@ai-sdk/google': - specifier: 3.0.61 - version: 3.0.61(zod@4.3.6) + specifier: 3.0.62 + version: 3.0.62(zod@4.3.6) '@ai-sdk/openai': specifier: 3.0.52 version: 3.0.52(zod@4.3.6) @@ -1485,8 +1485,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/google@3.0.61': - resolution: {integrity: sha512-jEKU1Mjcy5CoicejdJQIzM0ntYwyXR8vtYgAZYriKaOuLAiAhiiU538++fGU3CC9HJH/mL1OfsCwMM3gFiCNsw==} + '@ai-sdk/google@3.0.62': + resolution: {integrity: sha512-cC9HAjR5WZxjqGyEJrJqFTlVqyPE9UOFmmGdf5dINaimgfPmzqXYN1qTYEJ+1knbyTVsNMub0KAF5SOqqtO8IQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -14138,7 +14138,7 @@ snapshots: '@vercel/oidc': 3.1.0 zod: 4.3.6 - '@ai-sdk/google@3.0.61(zod@4.3.6)': + '@ai-sdk/google@3.0.62(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) From 794ad9410bbdb1fab10b923d0f5eabe81d0a6433 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:49:25 +0000 Subject: [PATCH 184/203] Update dependency ejs to v5.0.2 --- apps/server/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index 766b6234d2..4d352702f2 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -87,7 +87,7 @@ "csrf-csrf": "4.0.3", "debounce": "3.0.0", "debug": "4.4.3", - "ejs": "5.0.1", + "ejs": "5.0.2", "electron": "41.2.0", "electron-window-state": "5.0.3", "escape-html": "1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37746a1459..ed1131de31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -735,8 +735,8 @@ importers: specifier: 4.4.3 version: 4.4.3 ejs: - specifier: 5.0.1 - version: 5.0.1 + specifier: 5.0.2 + version: 5.0.2 electron: specifier: 41.2.0 version: 41.2.0 @@ -8137,8 +8137,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - ejs@5.0.1: - resolution: {integrity: sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==} + ejs@5.0.2: + resolution: {integrity: sha512-IpbUaI/CAW86l3f+T8zN0iggSc0LmMZLcIW5eRVStLVNCoTXkE0YlncbbH50fp8Cl6zHIky0sW2uUbhBqGw0Jw==} engines: {node: '>=0.12.18'} hasBin: true @@ -23017,7 +23017,7 @@ snapshots: ee-first@1.1.1: {} - ejs@5.0.1: {} + ejs@5.0.2: {} electron-debug@4.1.0: dependencies: From f285f7e14ff388f5fa482e88e311765351b7afbe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:49:57 +0000 Subject: [PATCH 185/203] Update dependency reveal.js to v6.0.1 --- apps/client/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 7cff4e1ed0..cc5c87a923 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -68,7 +68,7 @@ "preact": "10.29.1", "react-i18next": "17.0.2", "react-window": "2.2.7", - "reveal.js": "6.0.0", + "reveal.js": "6.0.1", "rrule": "2.8.1", "svg-pan-zoom": "3.6.2", "tabulator-tables": "6.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37746a1459..6a2e909500 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -364,8 +364,8 @@ importers: specifier: 2.2.7 version: 2.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) reveal.js: - specifier: 6.0.0 - version: 6.0.0 + specifier: 6.0.1 + version: 6.0.1 rrule: specifier: 2.8.1 version: 2.8.1 @@ -12062,8 +12062,8 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - reveal.js@6.0.0: - resolution: {integrity: sha512-RayDr1FL3Jglnf6p9xHGJ0U18va96PiuLs/JHnd1cdDOXvC+3lsXKe6ujl7PX0pvnhNW2Tpqnr6PEKpJVO2exw==} + reveal.js@6.0.1: + resolution: {integrity: sha512-9eacArNIgqO2HGWOK+93gJNn+gvdGDVbSq+i2u3Ja9kjiHps0XNLpgYTZTYjKRH91uXy3clGimeGiw4umHG/tg==} rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -27803,7 +27803,7 @@ snapshots: reusify@1.1.0: {} - reveal.js@6.0.0: {} + reveal.js@6.0.1: {} rfdc@1.4.1: {} From 0b5332fbb3dad1ad409be7d48f34c8b40ff89b8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:50:32 +0000 Subject: [PATCH 186/203] Update dependency typedoc to v0.28.19 --- apps/build-docs/package.json | 2 +- pnpm-lock.yaml | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index 3be2fb44ac..631578d9ec 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -20,7 +20,7 @@ "archiver": "7.0.1", "fs-extra": "11.3.4", "js-yaml": "4.1.1", - "typedoc": "0.28.18", + "typedoc": "0.28.19", "typedoc-plugin-missing-exports": "4.1.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37746a1459..b6e33e3e8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,11 +199,11 @@ importers: specifier: 4.1.1 version: 4.1.1 typedoc: - specifier: 0.28.18 - version: 0.28.18(typescript@6.0.2) + specifier: 0.28.19 + version: 0.28.19(typescript@6.0.2) typedoc-plugin-missing-exports: specifier: 4.1.3 - version: 4.1.3(typedoc@0.28.18(typescript@6.0.2)) + version: 4.1.3(typedoc@0.28.19(typescript@6.0.2)) apps/client: dependencies: @@ -10641,6 +10641,10 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -13236,8 +13240,8 @@ packages: peerDependencies: typedoc: ^0.28.1 - typedoc@0.28.18: - resolution: {integrity: sha512-NTWTUOFRQ9+SGKKTuWKUioUkjxNwtS3JDRPVKZAXGHZy2wCA8bdv2iJiyeePn0xkmK+TCCqZFT0X7+2+FLjngA==} + typedoc@0.28.19: + resolution: {integrity: sha512-wKh+lhdmMFivMlc6vRRcMGXeGEHGU2g8a2CkPTJjJlwRf1iXbimWIPcFolCqe4E0d/FRtGszpIrsp3WLpDB8Pw==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: @@ -26216,6 +26220,10 @@ snapshots: dependencies: brace-expansion: 5.0.5 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.13 @@ -29197,16 +29205,16 @@ snapshots: typedarray@0.0.6: {} - typedoc-plugin-missing-exports@4.1.3(typedoc@0.28.18(typescript@6.0.2)): + typedoc-plugin-missing-exports@4.1.3(typedoc@0.28.19(typescript@6.0.2)): dependencies: - typedoc: 0.28.18(typescript@6.0.2) + typedoc: 0.28.19(typescript@6.0.2) - typedoc@0.28.18(typescript@6.0.2): + typedoc@0.28.19(typescript@6.0.2): dependencies: '@gerrit0/mini-shiki': 3.23.0 lunr: 2.3.9 markdown-it: 14.1.1 - minimatch: 10.2.4 + minimatch: 10.2.5 typescript: 6.0.2 yaml: 2.8.3 From ff3b6c40112300a2b5ad2bff8bdaa920531b3d4a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:51:08 +0000 Subject: [PATCH 187/203] Update univer monorepo to v0.20.1 --- apps/client/package.json | 16 +- pnpm-lock.yaml | 1769 +++++++++++++++++++------------------- 2 files changed, 894 insertions(+), 891 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 7cff4e1ed0..2704c3f25e 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -34,14 +34,14 @@ "@triliumnext/highlightjs": "workspace:*", "@triliumnext/share-theme": "workspace:*", "@triliumnext/split.js": "workspace:*", - "@univerjs/preset-sheets-conditional-formatting": "0.20.0", - "@univerjs/preset-sheets-core": "0.20.0", - "@univerjs/preset-sheets-data-validation": "0.20.0", - "@univerjs/preset-sheets-filter": "0.20.0", - "@univerjs/preset-sheets-find-replace": "0.20.0", - "@univerjs/preset-sheets-note": "0.20.0", - "@univerjs/preset-sheets-sort": "0.20.0", - "@univerjs/presets": "0.20.0", + "@univerjs/preset-sheets-conditional-formatting": "0.20.1", + "@univerjs/preset-sheets-core": "0.20.1", + "@univerjs/preset-sheets-data-validation": "0.20.1", + "@univerjs/preset-sheets-filter": "0.20.1", + "@univerjs/preset-sheets-find-replace": "0.20.1", + "@univerjs/preset-sheets-note": "0.20.1", + "@univerjs/preset-sheets-sort": "0.20.1", + "@univerjs/presets": "0.20.1", "@zumer/snapdom": "2.8.0", "autocomplete.js": "0.38.1", "bootstrap": "5.3.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37746a1459..5630120129 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,29 +262,29 @@ importers: specifier: workspace:* version: link:../../packages/splitjs '@univerjs/preset-sheets-conditional-formatting': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/preset-sheets-core': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/preset-sheets-data-validation': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/preset-sheets-filter': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/preset-sheets-find-replace': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/preset-sheets-note': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/preset-sheets-sort': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/presets': - specifier: 0.20.0 - version: 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + specifier: 0.20.1 + version: 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@zumer/snapdom': specifier: 2.8.0 version: 2.8.0 @@ -5800,199 +5800,199 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@univerjs-pro/collaboration-client-ui@0.20.0': - resolution: {integrity: sha512-7FFM/9VdeFHioxSJ6r+t67kurzLA1w57O/J71ZC9goHxCf/rC61p/iwNKCSwlnrc73DCey39TFcZ2QdC/I9g2g==} + '@univerjs-pro/collaboration-client-ui@0.20.1': + resolution: {integrity: sha512-uPwtmx+/smG/Vcu7tfktNeSMdP6tY1l+o0lsaCJBe5pSaooDEQI6qe8qCX2Ndt7Ij11w/kleKHF3mcQHL2HNbA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/collaboration-client@0.20.0': - resolution: {integrity: sha512-dcBuRj5IodiIgGm6fWKW49CSvAqNJf6SjupSJaRDDisRF546hsS4IlulFEhlRaARgIlstqoM9aRDCjN4CPlfDw==} + '@univerjs-pro/collaboration-client@0.20.1': + resolution: {integrity: sha512-nZTtXLtR8mh556HgkHINxVfxZVO72YsN8hIAqgoVJeJFsAavcEypQ+ohlXvuIL9hxGo/Q3YJBnHZmgJmoZGfuA==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs-pro/collaboration@0.20.0': - resolution: {integrity: sha512-iCsyTAArvtvh6e4a2xIt6AvA5uHFUkh/t6mCXHR2AuBo1ZZud5jZTPnV9HkdLpf7VIV3qe+6KI7/N8MTOdemjA==} + '@univerjs-pro/collaboration@0.20.1': + resolution: {integrity: sha512-e6DupTLljB2VrQU0qiZI6CeMF3q4/PnBx5pxebsr5EmnJb7z4mZEmFijqccrdX148b7FlfWTmoEF9jEbKvkV/w==} - '@univerjs-pro/docs-exchange-client@0.20.0': - resolution: {integrity: sha512-/Ioyox2kFk6S8kwvmgPx5vscuccoee/8mpK4As7DAMxibFGbGvfWUfIbgnFm9k7FFNrwX4c/K6o30EekxTy77A==} + '@univerjs-pro/docs-exchange-client@0.20.1': + resolution: {integrity: sha512-wAguVjCF5PjpH7AAzrtjRv6Trnxy16iO5cMrlvV5C9ao09LWiz+QGdsDkLGXNqnVX3vSvGzalPquOJD889c10g==} - '@univerjs-pro/docs-print@0.20.0': - resolution: {integrity: sha512-CdcBxekBgLMJyMVq2kz523lbfxDXca/4j8oycIx8zI+RBDCRW2M+RDy2OcHaJ8Djhy9pnpCDOdD2k/FRvaJHlA==} + '@univerjs-pro/docs-print@0.20.1': + resolution: {integrity: sha512-eDBwg/tksTgCsrnX4ylI9WPSXEoylFSSeC4URE8HE9eoypzZcgAVNCgYM++lBR5w6jJl8W78vH6/DnyroAlfRA==} - '@univerjs-pro/edit-history-loader@0.20.0': - resolution: {integrity: sha512-0OvhK/z+lpSB4pvPhmzVLHEIva3tJ2y8QwsxEfTJ//V/cTPQFyLc3Iv9oLPlK/EeHurJq0eFiRskvtCfgaF5Jg==} + '@univerjs-pro/edit-history-loader@0.20.1': + resolution: {integrity: sha512-aIdbPguBNlhcKDWYqESD+XH6qq7VI2iWUhnFEZFe/aVRulJ7cesu2jOt6VrFNxOrEJQ7hdfj9LqKMnOgycS0Kg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs-pro/edit-history-viewer@0.20.0': - resolution: {integrity: sha512-tfZqyY3Sin0XwCHYmbza8w7bWhySUWc78RPMElzqgoQtJ5wHw8yQiKsK3kDj1piBTOkV1vzKpO116izXvqJaBQ==} + '@univerjs-pro/edit-history-viewer@0.20.1': + resolution: {integrity: sha512-nNS75RTZnqm4LREcj/pm9Bi3F+0wSltStfD+JOtwGxROQfAKDVt2d4xL7zMu0dEYKUee9U9LfD1tC5/cHL/I7w==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/engine-chart@0.20.0': - resolution: {integrity: sha512-PmBPBYe7MdTTi53oxiKxUur08wK1kKdMlpmAfbRKHM8iQ1jKiX+jppsiWW6DG4Zc/FUWhgTSo5Mvqp+EiX/wUA==} + '@univerjs-pro/engine-chart@0.20.1': + resolution: {integrity: sha512-YuX+iVKNcWWbAsV8bGsSzGEQpXvweSwRXLpXzQl7T51eoine/UoBfGuuwWVUDM2mKh+yh9vAJxjewXEzdx129Q==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs-pro/engine-formula@0.20.0': - resolution: {integrity: sha512-iHbQPkUsweym+APtkKR9Tvg6x9bkQMH1vWtEo2IBZRykG6Cg2ZH+m2mBjfGd+yar7hgsNybAvcGuST1NaIcKYA==} + '@univerjs-pro/engine-formula@0.20.1': + resolution: {integrity: sha512-2A90/aox3oBhX8uUwtPRULkKlE0LoimN8Gy59inY1gvuPddsys9OdBBCiuouxlOdGCCypZB/15G6L4NlQrfI/Q==} - '@univerjs-pro/engine-pivot@0.20.0': - resolution: {integrity: sha512-6eEceMhiMKkAwujDvcSjkgg/V513TpitItvvodfYk8RzCaS12XgCBtXcG4Hw4ckyzulFB4BgHZd5FjKH0sy6yg==} + '@univerjs-pro/engine-pivot@0.20.1': + resolution: {integrity: sha512-sULQneX4oAZwr/VlCoeFfvMjPww6mDMXuL5YsqZqQUh/0/hWjXj/XdWOxLh/RBuHeTd52s5NBiVXGoueqCynFQ==} - '@univerjs-pro/engine-shape@0.20.0': - resolution: {integrity: sha512-4fivQmgiAsc5UoAK9ZQSlRW+KtRztr1+NDDuz3WrXxmsIIcLEBRAv6aQbXOGA7TNOJitipnyIT3m3P/o2lL+6Q==} + '@univerjs-pro/engine-shape@0.20.1': + resolution: {integrity: sha512-XHQZzSBZf+gko9q8gfKg8VXZbndX8/02MqxAbqyGlJzDafBlSQ+o/R/D5C73bpH7EEx7+VVX0EZt+MossG/n1Q==} - '@univerjs-pro/exchange-client@0.20.0': - resolution: {integrity: sha512-sLSd/DX7InjDy5psH9zNzCxI5eTuaH6JkEECaf7L10Prmur7ug74FDZdV5ulfB7+xOcqVqT4Yd4UKVEIZYcfMQ==} + '@univerjs-pro/exchange-client@0.20.1': + resolution: {integrity: sha512-N2iPiGk+DLQyORd/uEPJ9lYrBu7unAVIOCqgQmiDKInyj1MXEdaagB2qsoC5b6ZvO7sjevRosdtHC1lYWbDivA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/license@0.20.0': - resolution: {integrity: sha512-JoppYlkQUuxhvuWWuGrGruOZTOI+ur08GNIFika5OhcSZBz8PTuvMNhSSxIPsSzv+ndK4y0uMUt9YYmRi6np2A==} + '@univerjs-pro/license@0.20.1': + resolution: {integrity: sha512-4sRYBA9PH1WnlDaLq1nh7W8q+5gHxkIkjjoMgeXrUK3JSJtLb+BeCR+AmMV5u6eKWwLVtB5ZpSjBFDvzILX0mg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs-pro/print@0.20.0': - resolution: {integrity: sha512-4Yd+auUSgLQ3QpVaHF1gAzrwjAp/lQD4u3vEP6lkPLBB4/rAhQchTq7LMg4fjALUFewYDJ8eCpg+dtpl7RE9IQ==} + '@univerjs-pro/print@0.20.1': + resolution: {integrity: sha512-OgS9YkqGe45z7/yFbq+bcp9br4gQxFb/XARrCGU1cdDB8MpSHm+5R0n7XXhz3YkFThPhZMh62FsLkkXagjlaRw==} - '@univerjs-pro/sheets-chart-ui@0.20.0': - resolution: {integrity: sha512-MlzmLm49u4wLpghsLFmDw7mHygeLZZh95AsMmdK5mU7SXv5Tles6b1pw2X3uJilX9vobLcLXuYVPcDzSZiqCuQ==} + '@univerjs-pro/sheets-chart-ui@0.20.1': + resolution: {integrity: sha512-awHhh5YDFW8+OcD+a0YBsgGRrkmp2srh3Ez9LoNBTHiOiqQoBmL0uL+cpQxww3XsJa2urt4g2RJECw1/05t60A==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/sheets-chart@0.20.0': - resolution: {integrity: sha512-AXTuqzfycKh0MJ05glVlD1uYmO3zPptkzu1VuPHlhFH12xdM+R1TK6f963K7apsZLuBWe3ZMA0pyQiiql5MQvQ==} + '@univerjs-pro/sheets-chart@0.20.1': + resolution: {integrity: sha512-ljEjVlCjMNlJ2gis1MQhedBk0EKaQWiW4LT47Uh7zj8Iir1qxlDVgnGeJwUipxMIt/4387k+c6CwFcrhhNyo+A==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs-pro/sheets-exchange-client@0.20.0': - resolution: {integrity: sha512-OqsqDnAKgOTLfqZpPfWAcf5+qTSfIN1He5Jk5ItqRFYZlok8aWwbH8fhsDPbfrg1Wq9e7Y/rljkvHFblXbFP1w==} + '@univerjs-pro/sheets-exchange-client@0.20.1': + resolution: {integrity: sha512-aXxFIMUQi+lO8+HaZvHvmS03YOV9Dg/+gVCTW3v1EeWTMtqr2OjepMmdR/ywOQn9fC0XgAOrIPzL/0F5qls+zw==} - '@univerjs-pro/sheets-pivot-ui@0.20.0': - resolution: {integrity: sha512-MfaJFY2hl9j3aiZaQmoTS7pnA/HtjqOcUnck26fQyMnA1GBkGFtNI4GXIXFCZRNukn16pIeSHPg+x5YBLNCw5A==} + '@univerjs-pro/sheets-pivot-ui@0.20.1': + resolution: {integrity: sha512-mBcYiyu0EAZohvkqCOBIVRggGJGTztG6c0A7L8DiY7OinOVx5eHn8vEO1+1SDH3wCYY+uzGRzoFamtXeqw2zww==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/sheets-pivot@0.20.0': - resolution: {integrity: sha512-P71zkINalbuzVoYsfOgBBoG7sJeeAZO96sAAfBPzh3wuPGIc3U2sSS72ZuNc1yJOEab9WWZk6vJ6e2r8YthX3w==} + '@univerjs-pro/sheets-pivot@0.20.1': + resolution: {integrity: sha512-GpVV4ORdKvMJR7yJH99LoWAk9ryEDiXEFX45xx0wsG2SKu1yRpJeDYyk63pcOHK19W53EebgRlHxfDoEs4QLqA==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs-pro/sheets-print@0.20.0': - resolution: {integrity: sha512-I014PqYG6a15hpLql7tPJOPP6A+Kf6BHgS4K4a5t5mk49f88mukpOxrA+vCFAl9UMa5xx9xF839mCJpc6c4eDw==} + '@univerjs-pro/sheets-print@0.20.1': + resolution: {integrity: sha512-x0u9KoI9ZzOGz9MQYOGkOiaUpOreJG0f4WJ4YqibINBPEEWMN9nXVpytyjK0PfUVv2Y7/8rKk9nvo0f12RqwZQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/sheets-shape-ui@0.20.0': - resolution: {integrity: sha512-rwOhfxBPdI3cuArXebDJI63/6o3iZNNatiNJBweZtLBx+x/dljc9CsLq1fitfxtnre0Tu4hS3ZvgRM9nOzHkdA==} + '@univerjs-pro/sheets-shape-ui@0.20.1': + resolution: {integrity: sha512-dyaJ9M/BGAamTnEhRsGfBPoNoIBeIwb+XOKhCjkZcN87Cb4zLWXV6rngiXClIQZTnxphD0B3g74sCFYSVG8j6g==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/sheets-shape@0.20.0': - resolution: {integrity: sha512-dSrxLta1r18IkfD+iAOHgdzY34F3xXPc+k3p/HsT//GMMh9o+KNYX/L0WURwgCgg7u9oJnKHec4HQx6Od8sJ3A==} + '@univerjs-pro/sheets-shape@0.20.1': + resolution: {integrity: sha512-HjXIm3uW8KX8zwpVABEQCEipDJgSsUEME9Eud7hiGhG22gBVMTtzmNUuSQWY/WDddrR8eSOus2rDVUqqJLi/tw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} - '@univerjs-pro/sheets-sparkline-ui@0.20.0': - resolution: {integrity: sha512-lFEcIRkWaMdTAojNJpepavP1eutH2cKCHmMyvBsyRKxLJPiShb8qGIsQGlAfVLtJJM7ms/ZrvQZpDRrf/C1v4Q==} + '@univerjs-pro/sheets-sparkline-ui@0.20.1': + resolution: {integrity: sha512-KbzL8pIf3kILolzvwaHfGTlQWClMKVRxb6Aart0kI3Qp73Q/Wx+KJd1R8m86bSt0T671MLhoClnwIkC1Qq6KHw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs-pro/sheets-sparkline@0.20.0': - resolution: {integrity: sha512-A86oJHWgLWRZz86PbIH73wkt6vblWY6bFkutcrAkNpMOahveVIsfbdBZHPPoUrzF1dZKnVwvfMg66qIWVqdJcQ==} + '@univerjs-pro/sheets-sparkline@0.20.1': + resolution: {integrity: sha512-bIvEK/cAf6cyVfPeYE1esa/GgNI0KnWFVfP3qoz2fPVVH8wU+prUounP2I4kHOoStAdiZixjfaGheYpyD5oLKg==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: rxjs: '>=7.0.0' - '@univerjs-pro/thread-comment-datasource@0.20.0': - resolution: {integrity: sha512-dWAKNE9wtKiaWArclHS4Cxd1XEEEqR09eo4Vs4yYTcQuv4xY+2Ha9sRbyc20eUWswtUO4hX+NE3b/9oalYT4ww==} + '@univerjs-pro/thread-comment-datasource@0.20.1': + resolution: {integrity: sha512-pcmSZ+DKrtGDJ/BfQS325jfhpJrCnisJ6XYPttVA1G4y5tBfOmRgG+wBeAmGqXg0IhAelyYEIvqXTxB4YMVaoA==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/core@0.20.0': - resolution: {integrity: sha512-sXz0qf0aWI9rEBZ24/+vUw/uMMwMQhKbBQz9YDafZF8MXl2ey1/n3GqXNBHrCKSpo25vB8rGT3sPk3PvZ7jWeQ==} + '@univerjs/core@0.20.1': + resolution: {integrity: sha512-grP1xH2KO/rmNyzSdmuryEnHI/4jH9Qo2yvvJhTup5950QNSX6UKXA/E3NnVj3TpflKY3kDnwugLJ+1Suw4IxA==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/data-validation@0.20.0': - resolution: {integrity: sha512-OZjXC+nsrnmlPXSrCsYQfgJjuAmZdzPfunx1zExSKrAcsvCdJHjvEAZZhURcGbJcPG4CRJiGafcm/htjxxwFEA==} + '@univerjs/data-validation@0.20.1': + resolution: {integrity: sha512-dlrb2kdFjL1sQzRhpluCSfGZsXEcq9UBr/ROhOA6EAQ2pMIP5Ndsj3ooChmGlqwZPwwQyCZ7YWKc9R1zEYEL/w==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/design@0.20.0': - resolution: {integrity: sha512-wXRi5Lnafnpw1SDij3zGjqmV+gwL3qj7+LOWXB9QQ6WTl0Tk6IILz/KMyUuEyPCOTryLQruJ3FlE7++y0ocALw==} + '@univerjs/design@0.20.1': + resolution: {integrity: sha512-VN/AQgqKrXzINc7TvTbbU1uTydaohkmhcdoPxdF2Iefh/ERkKiYRwW9hGkzRIzWi8eT/l/LX/nHf9hRo3NJ8xg==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - '@univerjs/docs-drawing-ui@0.20.0': - resolution: {integrity: sha512-kjNZWW1LklzEKM1BaCWTjH8wv8cqdxSiOBcBsr+SlsOmkqEUCh7EiLSoShIzKBNrGbz/fjEBCXh76upy0+jjpw==} + '@univerjs/docs-drawing-ui@0.20.1': + resolution: {integrity: sha512-MVeuO1zs5/sqn3exsrOZFZvLtmxuR2rP4bdDZ4WTnymLgMafXXqbeIpAB2KFNO3ybaydWrYyR3kLjVfMixlH/Q==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/docs-drawing@0.20.0': - resolution: {integrity: sha512-U1AeJFuPktZTWdT41gKMdmLfNxEBe4SvTnCsubl2nuwHxYud0RnXAywqD596MP04uDRjYknOh1vZ4i1Ba0pJcQ==} + '@univerjs/docs-drawing@0.20.1': + resolution: {integrity: sha512-xd9aPtA/VsVvItjuxsj2nC1lG0KqpLG7AQxbv/9b6YjGAVNf2Y/UR+4NjW6D0YQ8y9P63ls3rdhNGg0rS7epRg==} - '@univerjs/docs-hyper-link-ui@0.20.0': - resolution: {integrity: sha512-0jLJrssCuYZenCOFDJ1eMOgssU7HGDpjR3PByIfUca2nFSuAnLn5E/iSvYT+EZzUyB99gwiW/Tc5TS2Yj0IY+A==} + '@univerjs/docs-hyper-link-ui@0.20.1': + resolution: {integrity: sha512-2z2GX2/CT8LZ/hpN0XHKu9A/5VE8NGHkI8MmlVVNShxmGhxp3763maILC9Wx4Ji/QRz/5BgdL7kH+2ut+zzCpw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/docs-hyper-link@0.20.0': - resolution: {integrity: sha512-YrKFbL80evv9oeIjIFPkbYJKj3i5/V3/WB/rAQd2jQqqJ8sMt+8bPEv9y0s2n3ULoLEQoKwdQZ3Pqml/CVgTPA==} + '@univerjs/docs-hyper-link@0.20.1': + resolution: {integrity: sha512-WZF2esfWx3S3W7tj7MS3OvTpei/mpTkxN81ttaoUlHE/m8VfvJXFBIwDaqteRBp2FyOv8cxRWSE2ZCB1KackGw==} - '@univerjs/docs-thread-comment-ui@0.20.0': - resolution: {integrity: sha512-xW1vqIbmfLa74anstw55wdcZ7jzuKt9wR0a/1TZiECXRt9oY6QzVFNLJ7xA1cINGFbjRIiGfeG2vtvDAafmkIA==} + '@univerjs/docs-thread-comment-ui@0.20.1': + resolution: {integrity: sha512-V10nqNEObPjMESjqvTfG7G0LjZBJCExLxN04vWHocupQS8ao48I81D4Bl0j3/BhwCQ/+BRoPaAEU2DftX/iGNA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/docs-ui@0.20.0': - resolution: {integrity: sha512-CAkmK9BUt6ZNXpqSRvqbXX2rJ3jQLhQM38WvrMhebpmaHxi0eFMRGOJ3AHTwUne03weLLSK59JkyAl5CXa60yA==} + '@univerjs/docs-ui@0.20.1': + resolution: {integrity: sha512-UoF5mab+qXIiJfXasJKrfSa1Vcg4hdSBOj+xRLk5H3vV65PLoHa5apiA5MHanD46RPXyz58R9iSRplCbB1FCmg==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/docs@0.20.0': - resolution: {integrity: sha512-mLGOQEj7MUzWc4gDoH/l2phFyYjOHPCf9Jj8KSlQAOw97wvjSnXHXaRdpr7e6IM22481XVIR8kLW0j3NRmkUjQ==} + '@univerjs/docs@0.20.1': + resolution: {integrity: sha512-fHH4U1zGTz7LNjFz2YfyN6ppe19xTsJymsqbe3dvwL5rHp77+s/S2Tm7ObgI5TABN127N7sXZ0HlvulcZUNhhQ==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/drawing-ui@0.20.0': - resolution: {integrity: sha512-KrIGOeYYUbO5wXe7NLYg0RNW4Bi+Zwj2+xxiLjNd+KPvfmxOMnVZ0qy8T7La6ivPvXf255je4aLKNBir+RStXw==} + '@univerjs/drawing-ui@0.20.1': + resolution: {integrity: sha512-BL2DkE1OJ/b0k/vl01pLBNl5Z3LNBhoZ+2iBdPvPA05fwIJe0/S6IPuNOQ5JAGneh+y0hwoMr8KKX+yQ6IQxeQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/drawing@0.20.0': - resolution: {integrity: sha512-Uto+4m8WLCp3OaTrXObNG6SoTymgS1nz5LCwUk23FBrcd2JCfRiTBZ7wXEYbf90P8KSx32XBKUDtmyRQNR5bNw==} + '@univerjs/drawing@0.20.1': + resolution: {integrity: sha512-7DMc6U4tHGWK37EJS1vIaaFdqBf22WrOlAS4A9tbqyQLlqE+WhJf1nxtiO7EMAPZEfIHbIDsABUAHEDBHBQGVg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/engine-formula@0.20.0': - resolution: {integrity: sha512-2ZCz9lNIFie1ZXMTI6UWqwHQicjv8t/ixruSdXbecqW8+zDIktoI999Sm7EFEgK5KX3bUyhWI/NcEK4OWHERLw==} + '@univerjs/engine-formula@0.20.1': + resolution: {integrity: sha512-Fpc6F7PVLhWxlkNY2VMYssWfuc4TT0IcGemzRh12/HYeIEPsyBvDAnBzJ1OCEeFfP2ZvpoEC0QkhQJlv7UY2rA==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/engine-render@0.20.0': - resolution: {integrity: sha512-gIuYe41N/YIXwr6zvYn4+WIryeE37Xu0gUiopP5dbB2ls3fZX3mdBdw9VvP1DsZ/GE8Cz19OmtOePKnZbMIqjQ==} + '@univerjs/engine-render@0.20.1': + resolution: {integrity: sha512-kfPTAfJ7tMrVhLBIkvSZASAfB1m/s+TR0Gv8mv9S5lvUiNd+Thtlku/ZuDC0KFygHFLNXUqmXAOT+MMVJh7iqw==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/find-replace@0.20.0': - resolution: {integrity: sha512-PeG4FwTKzVq4ybZUqXHr3APuYHxRcBC1KTdlacAwbpzIW7nrTfkXP7+8ZK64VyL5pdfVi3wMOv7Usrra8XQk3Q==} + '@univerjs/find-replace@0.20.1': + resolution: {integrity: sha512-ACcZy+w8T/FW6DeGWxeejFY21m52InsO20qELZQqlSZsLaFCqKefYiZ//faq8kXKZiXNJQHVsXEPhHZu6OKsRQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' @@ -6003,156 +6003,156 @@ packages: react: '*' react-dom: '*' - '@univerjs/network@0.20.0': - resolution: {integrity: sha512-OKdkMmRTorIunrlijkWWkRfSHP1exy6JR5KLJDmZBG1x1xdTaSRtFb2Jo7bUQpZUP41yMnnEZDDHiV1ub+fnJg==} + '@univerjs/network@0.20.1': + resolution: {integrity: sha512-x2ySyo2qhoZ3FTk2L2Emko/kLoikldvTFdTD19iZrIqjReCz+B7lWeo1M7Biag3+AL5SpT1vaHnSjfIzYjeSkw==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/preset-docs-advanced@0.20.0': - resolution: {integrity: sha512-wrfOU8/iEi2IlJ3uEUybwVtDQVE+6VqBw2lg4BlaYkUs7GyAe12ou0eBjrnElN86C3GdVQPFUhST7IEDkcZtJw==} + '@univerjs/preset-docs-advanced@0.20.1': + resolution: {integrity: sha512-N2F7XVuD8HBOXWpkiowBq+OxE4O74AGx2QXEaj4FLLpOycnzUIiO4vgQLkikgQErSmY6TtqeYO2+u/sfo/dmHA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-docs-collaboration@0.20.0': - resolution: {integrity: sha512-GqqYfGmTpH1Z5q2won80aAVBG7VXMtLUa0GCSj5jmSN/Z4uOXPy432waymEz2+/4FxPWWWup/hmpBqLDKnsg0Q==} + '@univerjs/preset-docs-collaboration@0.20.1': + resolution: {integrity: sha512-tnHSWrRsIq99yYNhbHN975V1qXpkoUzyx+pZA+wtDNIxe/eWw9PYgapun7PmuVmcp/LB9D7oliPSQKubAGgbQg==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-docs-core@0.20.0': - resolution: {integrity: sha512-IVEwvZE9LmoRn+nLhJPolAId/PqgyxBCPOoD5u5dySQMY0aHUSGo+s2gEolK2neqSoF/PLQZZ495rqD/JrPCiA==} + '@univerjs/preset-docs-core@0.20.1': + resolution: {integrity: sha512-qwNEjqIpm6ypuao495YDmxzRIL5hqbZ/eqUCnzGkvy7LBBf8Yseh6wySroSdQ3CiBVAh9MtI/ndslGIlKMvJpg==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-docs-drawing@0.20.0': - resolution: {integrity: sha512-b+WTsR2Eh0L8t7cpFngUwfnapMg4NvEZ/K8m8Bmyq4WB1kkExvTUuHqcpHP3IlVLRSFYyuMAQme7JbO3rC2MnA==} + '@univerjs/preset-docs-drawing@0.20.1': + resolution: {integrity: sha512-5xTglZKKVxgzjAvzFntyU7ewBk88Wiib2CUiHTYG7/zfbkrv2PguzAB1iqKlZHFp3jnaJEDwKoOVoatTLwHaxQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-docs-hyper-link@0.20.0': - resolution: {integrity: sha512-8gA4BCqImWQ/FLzdnpc5OeHh6DKuP1FPgqyYufQ0XfT7QXDn2YGy5tgMjo6wpNxOl9ztZ4PRXyGG05tVq84ywA==} + '@univerjs/preset-docs-hyper-link@0.20.1': + resolution: {integrity: sha512-5ZK4ZwD6lEcqWYgs9bgvS73GmKO9bib7pE/X0dwqSwjXI82gZvCa0+46KpaK66dMlb/VC3BzXvOJjSpEwUnUQw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-docs-node-core@0.20.0': - resolution: {integrity: sha512-unsiv8aMsr+MF0uPwH0vwuRWeNlhnm58TFTmrRrNcjyV/jloxdQHFXu72kWJsmOOxDmkTgZW3Hb218dfGAtUWA==} + '@univerjs/preset-docs-node-core@0.20.1': + resolution: {integrity: sha512-orRgG/8iz0PkikemuPU6e42qx+pmdfhW0xxJdTLVi4yq1wCt/r8+j4Y3dCS4Be6ktbmCPPrGVVL+0PCSXpQaDg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/preset-docs-thread-comment@0.20.0': - resolution: {integrity: sha512-zNrH1NNMBmM8Hh1Se6kERQF+m8QcmSuwafxSLt35LZE8SWD4QiDkGLTqmXD86Q8C0q8R+Js2ubQAy38Y/S1XRw==} + '@univerjs/preset-docs-thread-comment@0.20.1': + resolution: {integrity: sha512-VdWfRyutjpHqnH9uvSkYREeoPQh2B01ptzfDWpyqfsqlDrR9/botv+edlelE7/gmdj2P6sbvz1Bo75ABurmLQw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-advanced@0.20.0': - resolution: {integrity: sha512-5+spZkgX6poD425SzLvIq9zIjCCAVkNqdJNPTY0VBf2JAT673VeFfclUiPlPUSi96OC1CgSJkOrab1c0zz0R6g==} + '@univerjs/preset-sheets-advanced@0.20.1': + resolution: {integrity: sha512-dzFNvkzreLqi1tb5V43GRRsho0qcXT4GLY01O7/L+c7WwwSdbi0gkVWuu/ovXYNj4c5Cg698bE/dYT0818NHrw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-collaboration@0.20.0': - resolution: {integrity: sha512-kGtr9BFnuqUhuPb8hbb+9rtGF7c5Yxnaz+qcVLpcG1SA/Hqj83Zyp8x+JRElS4FpcJnHSLx9OD74QhQgo93vMA==} + '@univerjs/preset-sheets-collaboration@0.20.1': + resolution: {integrity: sha512-+YKa/R8UDjvIzSjYcpVMC6TSxPLctpqoAoCz44vqOF9Ny9qex9Qv4OMbHvHIFoJ3fq6Bztaw9cKZ4tjoJXqf6g==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-conditional-formatting@0.20.0': - resolution: {integrity: sha512-vpT2KGFKRZEPHJ7mtSFNm/AaW3lyr7jvmOG9XnH8/rzhTmzE+XrN/uy8N+5SPRgl6MTg/czUlE4YaeZs4UwE/w==} + '@univerjs/preset-sheets-conditional-formatting@0.20.1': + resolution: {integrity: sha512-ziF6RcYBpbPFCPIgnY72PVZzYCfL+NCTQVJY4uN8gEKmGwIVR64q23mL2+WD1a+Xpc8KA3FAOl65YZybDAsdiw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-core@0.20.0': - resolution: {integrity: sha512-UkVhdOyswfDfkB5WyvGocVgUfAvWrvcC8JLgw1VBV04ck43g5RzXQGy2h/Fyxztl+6FaznimPH3QuPuVYjEN3Q==} + '@univerjs/preset-sheets-core@0.20.1': + resolution: {integrity: sha512-dlY1wvy2IUTo/ij8JkHYl0lBTRGktrtaOVKK3RunO65Fwq5S2EQ6Ub1xKMotrh5Mbk/GkU+qNIkQzdt4vThVjA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-data-validation@0.20.0': - resolution: {integrity: sha512-qGQUnhveNgzWvk0PFS+bbv8iz0EfQGMiNUXBfCwUcH1jnsYZlPPHy+BBMxexEh5mnz2heHTDR24tbBk3oeG2yA==} + '@univerjs/preset-sheets-data-validation@0.20.1': + resolution: {integrity: sha512-9AgmEp+25u/H3X7SMJH/nk1NX1jwcXA6DY65UP6giWIT+F9UrrxLN3lrSon+aWnBgrK0BlRYYt7uL5JaBboYpQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-drawing@0.20.0': - resolution: {integrity: sha512-4duVTQkYbvh+En+89cE3Ip00OuXhemIsglEM7TBPywUg1v3F4zNBAgzS64SWf1CLZMo1UYjouemCPOuNWalnJw==} + '@univerjs/preset-sheets-drawing@0.20.1': + resolution: {integrity: sha512-MCMPI4QAnAldwqu4td8r60yBJljYBwAzk4qOuFxQ953veizQu8LINg2Af48J4G6Xqrv5u7FWsIznbsfgRKzx9w==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-filter@0.20.0': - resolution: {integrity: sha512-WqqxuRhiIFE2pbsf43SvIddUzt9riRFS74/Lf4UOqXrm/SYa6BtwM+cFfLnxr5fZxBRl6iOFUNBByHDclcIbxQ==} + '@univerjs/preset-sheets-filter@0.20.1': + resolution: {integrity: sha512-PJT9T/NmjngvME9p1Ivfrx8Tyc6y7IslGLXewAJNk5AcWKD2VYXvCX8IlDHLcZX4iGD4YhVFtwhn+F7lrXuAOw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-find-replace@0.20.0': - resolution: {integrity: sha512-pRNe7RxiijgAgyqEs2eis7UpMRm7vEWIJeA2F5ZeVvNh7Po+RWgpZR4ZBX939nampc/AtK+zD0Pt4/L1Op4X7w==} + '@univerjs/preset-sheets-find-replace@0.20.1': + resolution: {integrity: sha512-JFLA2yIlc8S1e/VEsuqHGS1EIjiLtXstngEqKOdZX5WzZuP+8v9CwHmV7Bpu9UkGGfz7aMYHFxb2PPWcQI+9lQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-hyper-link@0.20.0': - resolution: {integrity: sha512-z2r/ffTjetkAH6H6slWoq5YJ6iivT4fEJ8w7kdoXLWUVCBaCOocfVj/oKoWsQ6A63yAKzoQcKWOBstKAY/knEQ==} + '@univerjs/preset-sheets-hyper-link@0.20.1': + resolution: {integrity: sha512-W2s7kB7CLxFRWBHmA30QlHo8eUz2GRsHL5eW77D+dpNGgyLmzcQHVGSSwDFtx9z4nl1mjA+VU8HVsvCjElpnpQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-node-core@0.20.0': - resolution: {integrity: sha512-sJVOc7jTUAPSQgpLCBjek4DlNwblSWGrtGUS/aeprKtNIYXnykXvSpHAXm6Y56VEM1RunTev/kqXQtzVnw8trg==} + '@univerjs/preset-sheets-node-core@0.20.1': + resolution: {integrity: sha512-8cAAT3a8tEuPaMUSqvz0f5cacZQ3d4PKG3PGVBnFVo3W/dACfeLj63MR9qUQ69fYckRVdP8mZPe8+czLx8dUaA==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/preset-sheets-note@0.20.0': - resolution: {integrity: sha512-zFNLPSoHdMQvn2Z+s5c9YTMtMagxtbas7yF/fRuA0AQZdkIzuM+OdPwV1TztWcGKhAmFfhaabDW9eTofwCBENw==} + '@univerjs/preset-sheets-note@0.20.1': + resolution: {integrity: sha512-tmM2J5MN1fdr8nIxY0VWBfJ32osfUxMhTVJCtrs2OdwJhUsecK0Ne2tVZPNhjPbTscO3IXCRAZvTsfBDUQqmuw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-sort@0.20.0': - resolution: {integrity: sha512-SmtU01v6XLYukhJdiAwJ4ubDy7fcCC7dWGcLoxtpZwqo7KegsQnICW+2ZqpvyWqgZwSFyG7s+st6Tp7NI0+3BQ==} + '@univerjs/preset-sheets-sort@0.20.1': + resolution: {integrity: sha512-TpPaI64fQyN6a2XQk1Ytgfi6N2Y1hy0H4KYfmdcUTjhfqWwCsu1f+TGvbxCIOaxfkHRKKvWAlQ/sX0+UMGZihg==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-table@0.20.0': - resolution: {integrity: sha512-seFq1ptOzPuu05y1JXBysGjNHzpfuAUA2v3zquEVlC1QAc7gE6xRyq7tDxlkF9UKd1RSl0ZBJHKHEeIkVuBGYw==} + '@univerjs/preset-sheets-table@0.20.1': + resolution: {integrity: sha512-uvgHLQpvl5n3jhe1jlS8L+ml0RSqBS6Le0V5heFnibxV3s0RL8M+NpeogXtGZAcR1s78RLxwvhL59FVnW45eHA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/preset-sheets-thread-comment@0.20.0': - resolution: {integrity: sha512-dDr18hv+e00lkiSMKbeisKa4qCTkFI1b6uvGKAfpjem3Td75AqCo0aldliu9bdQGNMDFhTyz3n776rF/9+zIIA==} + '@univerjs/preset-sheets-thread-comment@0.20.1': + resolution: {integrity: sha512-FXOm89Lx9nYi8ntFUONy1UysDzcfBMth+wGT5iHrQrF8wIMHMAxRuM5fm3Wi6EbaYb4IZZ7g7iyP8+M4f0reTw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/presets@0.20.0': - resolution: {integrity: sha512-nwry3E0W/rz4z+Q4QXHm+f0om+wo5xK+gd6cw/hSMF+cFzQfPwW1FLx20wxHvb1MlgIYF73cMgan93VhpJ2/dQ==} + '@univerjs/presets@0.20.1': + resolution: {integrity: sha512-t9nsFZxzjr/aQT/7RGPum0SRvI8D8647tL55+Is7D1D9H2J2A0syUt5W55tBZpodl8S3bCMwIGHCR+JYpckqNQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc @@ -6161,171 +6161,174 @@ packages: '@univerjs/protocol@0.1.48': resolution: {integrity: sha512-nFHNtGAWOV0u1+IqoznH9K7hV/M9OZ61Vqwy8JMWKlgLLsx12m3vJqodkrVlLkI2YU5WuwjaUT1+J8/nM+kcUg==} - '@univerjs/rpc-node@0.20.0': - resolution: {integrity: sha512-ZZJkkpLg+lZbloxY00oOCwHTdgnXMJU56OMMDcbYXjhMtPTgUvlOhU7IdaYoqj9aFAe8VDoCQERo4ypwuUlImg==} + '@univerjs/protocol@0.1.49': + resolution: {integrity: sha512-xVKSmSCQTElzozs8+zCWDdx3OJU7u/s/O25r7h9r9/Xot5tr0yTOwdjeEpjwjUCn1L1e67iBPKLkqegq9h4U/Q==} + + '@univerjs/rpc-node@0.20.1': + resolution: {integrity: sha512-kR7IDVmeNHw7fk4IHTnXaeSUQJNEKzJ8HeyZu5xv1AcKmq85eCU+AA3ZgF9BPGMhnat9FBTqRfHg61GSTvHKwg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/rpc@0.20.0': - resolution: {integrity: sha512-9sLzahk/zSlS5ajJjOMD5T/f6IeUUOiR4IZiHfLWFYRBr0EUHr+SxnnveiPwc5kWCER39Z7DGyqYCC7Z0c0NBA==} + '@univerjs/rpc@0.20.1': + resolution: {integrity: sha512-4l0uyHWKD0dDl1YT+7YaBUxV9DdKRBO+WSpKtPxW2dLIuBgEwN0JAbqnVC7DpXUalrzWtqMUba7MM5p3Z347rw==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-conditional-formatting-ui@0.20.0': - resolution: {integrity: sha512-fUeL8u9SspV/K+ZXxLojS5jvDbpYa53uvyytoX9sZeHm+UUTQ3k5omW4JGmzW1lTmu+VWVZPFIsPgDN+CYOOvg==} + '@univerjs/sheets-conditional-formatting-ui@0.20.1': + resolution: {integrity: sha512-K6Z9e143hjBb0UD2s0v6xnvxYN6mUjt0secNWHbYQDvsPUBISkv+E/wKb5EkwxgFtwTmufzWnMu+CtQhe2On1w==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-conditional-formatting@0.20.0': - resolution: {integrity: sha512-BqgAi/H8iKME6G6geRItaUUEoGliafN7tJAyOAx7pjjJlfDsZ0gI9crTnttUDvqOFcaJcWZZz8fWL3HCIlQigQ==} + '@univerjs/sheets-conditional-formatting@0.20.1': + resolution: {integrity: sha512-Ifo0NqVV8R5uQva7ribBIHzGl8ZPzgHKG1ujHy97+u7tGVX8PgOAi//hGkgbzRyjId2Yyeweb+OZc2FRJML/2Q==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-data-validation-ui@0.20.0': - resolution: {integrity: sha512-b42nbURw/CrRSQWmxL0/FkJ1eQl/FAb4IM5bwRVcBV3al1ucD/qKjKa3S/zbdeSNv/uk9ymu1syipUGJYCbikA==} + '@univerjs/sheets-data-validation-ui@0.20.1': + resolution: {integrity: sha512-vvLQ3e/5LjIo9LK9nil0xVHR4yAZcurq4UtUrfk01QumxVcdX2NDiYGidMVSYYotUOn56iWnUtV6oALWeIXSOA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-data-validation@0.20.0': - resolution: {integrity: sha512-w3rRKLhXr3Ce9UAy/WenCmVeN3Jg6OgayNYORq0SCCppoqJbLMzjWHz0mU0T2ZPrgioalfxdbKYPGlCIxbDluA==} + '@univerjs/sheets-data-validation@0.20.1': + resolution: {integrity: sha512-uryXRc755GB+dIMPA3erBuzH8ZFgLn6hcUjmUJVd/5qJf+Gz7PBbsgq19xqZizuLWAgLnValemtx5+oo9VUszg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-drawing-ui@0.20.0': - resolution: {integrity: sha512-c3vnt5cNm8ZL7UjZOyYawF82oCm9pQG1ld8g9j+NgP7hbsiJlsJRyNk0rHKfxBbsvADC96RXy7KJDL0yxJTx1Q==} + '@univerjs/sheets-drawing-ui@0.20.1': + resolution: {integrity: sha512-dq/1c0kgiK2n5FVYXm1YeWaxgqIZTi+HWANR2KPnWcBq5NkEPk4lqnPjEHllXjJJVzLm2TCH4FapOWk/vT9jTQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-drawing@0.20.0': - resolution: {integrity: sha512-/8/P+hayAL4Yp6CsPwKv3OiyD/tBbWHIE122qbU4QoOZgyscD9DVzV+K0M7940T7EcJ0xZbxZHlGzHhl54MmOA==} + '@univerjs/sheets-drawing@0.20.1': + resolution: {integrity: sha512-wHV28DPmkDy5r3uRVk+5I0pNciaeUwHQ0OOfHtRRHVZNCyEe1DRMd0UWIbW74dwNOz5Y5v542nSYTGmbBk12tg==} - '@univerjs/sheets-filter-ui@0.20.0': - resolution: {integrity: sha512-hgdhdBSe2W5H08tef/2Tgr0m+VrtNtHbiCWNacJf6z6p9iZ1t1rWMI3XheijqH/MX6W6/FidLJxK3i9iC51Cvw==} + '@univerjs/sheets-filter-ui@0.20.1': + resolution: {integrity: sha512-7dnrH363+Wjk6YHl7GLiQuPTJZsXtKSYGT8FGnrA0TUmgr787rtsSlzGNLEWlkHut+Xw7+0/mT8hdDpkIbSE7g==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-filter@0.20.0': - resolution: {integrity: sha512-WJKOjHz/3Ol/j5mkM/h475wWzWqfY5mVo8q0y0OzO7aOCu4mjHF4xOPr+X6Q4m7CTnK7j++UMWEF6XIBPSPEGg==} + '@univerjs/sheets-filter@0.20.1': + resolution: {integrity: sha512-RqSt1TJLYUZTSka6/ypdY/lDVNsrh7fmSNaM1GGR+98D7WSkY3nzeEVcKVovU4Errrr2FmWQADlZj8wKM0ok+w==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-find-replace@0.20.0': - resolution: {integrity: sha512-foz/7cRPvTmu5EwALfIecDAyYVMxmEdBe+iHruRc0WCcnRuSdi1l44g0/GPdzJbZalWejGw0ocHnKgYVPptgHg==} + '@univerjs/sheets-find-replace@0.20.1': + resolution: {integrity: sha512-w0r4JRDr9yXUa6OOm8+CceRNfvOAttPoFCR05FxkkDRwcqrPK3azGZbS4F/Q0LXv7A4E8tFg8OPHtSnHBTpwtA==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-formula-ui@0.20.0': - resolution: {integrity: sha512-2085X4qBcMWm3DQxJDzVUHWBJotPg1aacGPg97MBIRjfk62Afc+Szj60r4n5xOMt3EwSMnipyuidzkgLqWwXag==} + '@univerjs/sheets-formula-ui@0.20.1': + resolution: {integrity: sha512-VhSthFtDXhK/B7cqpu2hxUkAc927U4GOY/vPJQlZoEDq4xM4yq0U719BEr7xI+FKfgYLA1j4USwX2Z2OffO75g==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-formula@0.20.0': - resolution: {integrity: sha512-ERF8O9itqpEcVuz5hH0HHe/UQP0o12zJptk5TgWN7QaZottcZ/mZ4iJjo1FPI1Rv/MsfZtWEFpooUjaPRqXMQw==} + '@univerjs/sheets-formula@0.20.1': + resolution: {integrity: sha512-AESehaGW0PFaS5x8maEc0WzjmIcM0ZGvO4UPw6841ROSf7yVHpVSxgAfiLrO59yyYc6fQ1P9rF95mrkqpl23yg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-graphics@0.20.0': - resolution: {integrity: sha512-oDR/4pTQ/eTW/uKSmkppZOeXgBVN44D1pX6c7F7YSXAlTV3JU3HZ3FeGEB/fpxmjUV512q+HiCoG7VQnyYZcEA==} + '@univerjs/sheets-graphics@0.20.1': + resolution: {integrity: sha512-lOkOddCcIddxlc18wjoJQfEVr8IrqMXcZe0jdOM2BIenfa2jo0JzbEGlIBmEHE6yB6YGlE178BqlA+MNKKoRZQ==} - '@univerjs/sheets-hyper-link-ui@0.20.0': - resolution: {integrity: sha512-9RqnKqbQnI8F1hrkdD2IIw4zm2IpzNqROSFTbZub6puhNWBgffF5MlK2mk1tG9wiEBScBxfwVEa2X8xdYriUqA==} + '@univerjs/sheets-hyper-link-ui@0.20.1': + resolution: {integrity: sha512-1fJxbBhi/E8krAAtdNaE2C+wgVjy4iQNkGHdXxc0UIBw278V32sQ1b9Zp1ezZ8osz3hVNrkvXz0TPBJM8moqVA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-hyper-link@0.20.0': - resolution: {integrity: sha512-VidctE2XcmgEYxzjn0piVtlhnbJzdZSZa3Y1Jb1XnusDh2Kv6RukYma3vyact1gBlWg9QX08kaZzQhJqJ5LlVg==} + '@univerjs/sheets-hyper-link@0.20.1': + resolution: {integrity: sha512-qlDaU6ggk1rWkxl+LKHNQnqtMHXpFetoT2xvzgLveCSja6wWDP8HEHWoujtd19S2P3ia4NJutJ+r3QEZZtU4DQ==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-note-ui@0.20.0': - resolution: {integrity: sha512-H9ez9hpRL0tOycZDPnZF7195TmSMFi3YNrZymc/VtqLPsYOMl4tfHub4VVDQ97EWbKpgh5nLgVDD17Y29kHsbA==} + '@univerjs/sheets-note-ui@0.20.1': + resolution: {integrity: sha512-znI7bZ39M7F2oWHOeLKhbf+u3MIOVLUP/MsADH+EllVbVAx4nas+S9RJJogD9LWaTvetcs+jd101iFZBgFPVFQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-note@0.20.0': - resolution: {integrity: sha512-PsjUtaw/69wlKNcSUdDmZhQp1NIXMwQbwyP6fl1MyvZRlj6t9YS0wfyIt1Duy3L4e+AvPwL5E3hNdyTIyl+u6g==} + '@univerjs/sheets-note@0.20.1': + resolution: {integrity: sha512-kPUnkcWlqVthz/2r8A+avEd3XersGZ8GVXgzj+eYr8iPoJXoZBJVABD4iezAOHgt3aM/q8uwWWU86CnoaqaLcw==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-numfmt-ui@0.20.0': - resolution: {integrity: sha512-0ClpQx9jM2k3Yk+hDu6RXVpDQ+cXT64jis0dfCazblBnwNPVzKHk+wMYFQGT1sdJTVeD5/VzsPVgQVxhpsfloQ==} + '@univerjs/sheets-numfmt-ui@0.20.1': + resolution: {integrity: sha512-VnCMPVI8qgG/sMCMmayG4e7MCf8+v0O5W05U8tuxP1kdJxuBc+RLgllRNLHJ6Ca+Rxm0ONSi6Dqd7A3otxeIFA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-numfmt@0.20.0': - resolution: {integrity: sha512-3USGuBFf/IS4Ui7IpFmStcj89Jnv7N37d2dhzzYQWADS0xHn1FzHbIZUizQ/kLMMacE3758ZlhEwYnkcKWHJ7A==} + '@univerjs/sheets-numfmt@0.20.1': + resolution: {integrity: sha512-cugnl9dHrrOhTg0EeKIubQ1wBkbN4qYu5pqM2C1F0/KmyZgnHj10coGxMc6nwvoTrFJxbU+BugR60xA2dDIkoQ==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-sort-ui@0.20.0': - resolution: {integrity: sha512-tyB0yTpMrj6ZTV6TXTRtt0hAWcJ1if8sIMJBfGI2Jhx+2VzXjYn5d+SzvVYgiLNOXkBFtex/J+jLA876SGlEmQ==} + '@univerjs/sheets-sort-ui@0.20.1': + resolution: {integrity: sha512-wFPP8gHqbDgtjnCwybr4QMWD3xhkxdgtJq0lCY3IXzrSLutGEoiUyNWIdEFUKx9C9uV39Md3q+MxV13mqewA9g==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-sort@0.20.0': - resolution: {integrity: sha512-Z6eeGRhQZyNfnQ2sfdV6qTHUdlcadQNAg9An4qB2nFn5QS5uvm2OcLd+sRteVqA2TXzP6KlZxQmFV+waiENaWQ==} + '@univerjs/sheets-sort@0.20.1': + resolution: {integrity: sha512-+hqCd9GOcZTSpVpPm+gsVOpE0grV49hgg16mtnlr24Gf1dFcReWEZSVYGtIY2Ha8dfsDfoR8oiiYU0c2LEyQjQ==} - '@univerjs/sheets-table-ui@0.20.0': - resolution: {integrity: sha512-nycmmfgfNdGxsYTqZmAxA93QsHcCImRm8dVFVWy+WLWle/1i3Ol9EfiS1Us9Tau8QDjvtm3iuzi8IPm/nfVluA==} + '@univerjs/sheets-table-ui@0.20.1': + resolution: {integrity: sha512-JcWxA40Q/Fg1YAnMCdc3W9WaIdHu2FElTZ61m6W4EFzrgzefIpOstlqoPdyGg3+76eqyTnxcY5BFYFYeZwxeUA==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-table@0.20.0': - resolution: {integrity: sha512-mZGVpQYRTApPC1qr2rRhrZJMG9fJ8U9M30y6r2ZEBRIwpZJLIo3Wpt2dfhDP7RsPEV42BgInMDiasxOpMbaaJA==} + '@univerjs/sheets-table@0.20.1': + resolution: {integrity: sha512-pC61Dn6ZChYvC3VNo6TLJ/GmuCtfJ8P/PbWkbygI7KlqxiNmMaGGq7auBTAPbUeDswV7mimeh4/0c1llQntaog==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-thread-comment-ui@0.20.0': - resolution: {integrity: sha512-aqB/UPJFxyJLd7FUAJbAFIP23FBBXwuMiHTjIC5UX9IS0J54NhJRgxoiAAxMXDf7nspUKo1/hI2oLyfvpD8zLQ==} + '@univerjs/sheets-thread-comment-ui@0.20.1': + resolution: {integrity: sha512-x+7+LJAftvwbokFdofGU3UwIsMZE+W3koDhthe+tr9MXC23HLOe18nN2XpJtihlxCAOfqUMYvCUziG+Mr4u7Cw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets-thread-comment@0.20.0': - resolution: {integrity: sha512-SczsVh6O+ueNKScRRzFpslMJiTTowJWdaKgK51pYspNx47GvPwhLRVBW267jds0VTzVSlG24oC+JxKFFHgwgxw==} + '@univerjs/sheets-thread-comment@0.20.1': + resolution: {integrity: sha512-o24gis9YKsGyX3XkuJ6lZAaH35m3Yti3egMAaXB5FxwrP+G0Y7M14GYEKXvWSCrj0DuDGt/fKfQxAAOh2B4hMg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/sheets-ui@0.20.0': - resolution: {integrity: sha512-HdLBYjJQXq5QhAlpW1srUe1K9TuHFh4t+LqknFb1Ep2OQ6naHbb38bIXOPDktOQrmtwuWGyHNGJxbwC7nfGDoA==} + '@univerjs/sheets-ui@0.20.1': + resolution: {integrity: sha512-jZlmCVpPJoXL1v6z0MsGDT0XWgKgcEZvifFr0UaYJsFoTMzu1LkqZw5YCudrQKY+lURprLzkI2lT1T0npSXwkg==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/sheets@0.20.0': - resolution: {integrity: sha512-1Z02wg0zhigqwvJ0FWDnH55jFpOo04JrTUZQckYymgLyHazLHkJZAy/+ixyVcAj83xNKPYMaj1Q+Y3iHG+V35Q==} + '@univerjs/sheets@0.20.1': + resolution: {integrity: sha512-wxEXUJ0hJkkIifzbyxNBUgHiY07v71EIb501ltcnT8d2JUoP6JiS9tLyOvHsLK6OvvBKXNvohN0WbIUHLlXiFg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/telemetry@0.20.0': - resolution: {integrity: sha512-zDwdeBl2vDnQwmbPSrMfNbef8fgzHVbnhSEItl5thvQQ7KIZr0sR7XeRXlI9VJGOGHg1WruJQ5zDdWeNQhP+Ig==} + '@univerjs/telemetry@0.20.1': + resolution: {integrity: sha512-84BYQ80jiKU2sYEdfNMXk+3lANTWjGu4nDYNMeilTOlHLjpL925BnAxM0owAnql7jgzohRG62d6gtfukH/Qp/w==} - '@univerjs/themes@0.20.0': - resolution: {integrity: sha512-dJ9m3mY1uhyhvozK6m0qsnRF406cIAMBOAh4qBo7N1h4G9Ff9TnUTN/ebF1AQmwOoAcJkxmDeiD0Ordt1KLA8g==} + '@univerjs/themes@0.20.1': + resolution: {integrity: sha512-/CK4jt59WeY+WkpXJxY9eVknGM/yFZOgMvprRyEAS0Vp+VYfV5w9dqLm/Z8zZBdWtgToteU8XfBQNNKYuaefpQ==} - '@univerjs/thread-comment-ui@0.20.0': - resolution: {integrity: sha512-wBGDtRXcHCW+7hN/bSnxtLggWVkTjKt245G0FUCcWzMQV2+H7Lnlt8zpsGE4kfILIbZHjzefuPpX4qlfZcwBSQ==} + '@univerjs/thread-comment-ui@0.20.1': + resolution: {integrity: sha512-Ci8IUblnBRPYzFvngTIpn7sXyeu+tTgoZDp9t+v3UwZTVf+RO0hq63cTKGc1jfLOC9C+3icQGoaEPfgRYw3cuQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc rxjs: '>=7.0.0' - '@univerjs/thread-comment@0.20.0': - resolution: {integrity: sha512-F4iARrow5esrb3uog1IPSKwb1WaZCCBTunNpgGKxhic4LClyZW3tx9oO0QdUaaQWBCNiY++J2jBs/ktTD+rdmg==} + '@univerjs/thread-comment@0.20.1': + resolution: {integrity: sha512-jiDS+pQ5d1312tz2AO2ry0bLd0qkjjAaqjsSH9a87H4vSYwAVgbrK19m4UrTadYouL6LspxqwylXa7xsf9YXNg==} peerDependencies: rxjs: '>=7.0.0' - '@univerjs/ui@0.20.0': - resolution: {integrity: sha512-XKxcH3hsYAOhL9wswyMglrHDRzCweUFetzwdlulaaztgwp5mKC7qNp1qgy6HUBb9OaB1yZYa8raBg7dN49DVvQ==} + '@univerjs/ui@0.20.1': + resolution: {integrity: sha512-EsDteeB9G4URTVRt3u5rSC8M5xEnOllHDEq77LCqLY34fZxQX5qpnb3z8IjcRCnQWBFM+aFfzMwqmQ1Y2cqZ0w==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc @@ -19465,24 +19468,24 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@univerjs-pro/collaboration-client-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/collaboration-client-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/protocol': 0.1.48 - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/protocol': 0.1.49 + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) crypto-js: 4.2.0 react: 19.2.4 rxjs: 7.8.2 @@ -19491,46 +19494,46 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/collaboration-client@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/collaboration-client@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/protocol': 0.1.48 - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/telemetry': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/protocol': 0.1.49 + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/telemetry': 0.20.1(react@19.2.4)(rxjs@7.8.2) crypto-js: 4.2.0 rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs-pro/collaboration@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/collaboration@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/protocol': 0.1.48 - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-conditional-formatting': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/protocol': 0.1.49 + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-conditional-formatting': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) uuid: 13.0.0 transitivePeerDependencies: - react - rxjs - '@univerjs-pro/docs-exchange-client@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/docs-exchange-client@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/exchange-client': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/exchange-client': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -19538,17 +19541,17 @@ snapshots: - react-dom - rxjs - '@univerjs-pro/docs-print@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/docs-print@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/print': 0.20.0 - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/print': 0.20.1 + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -19556,50 +19559,50 @@ snapshots: - react-dom - rxjs - '@univerjs-pro/edit-history-loader@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/edit-history-loader@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/edit-history-viewer': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-chart': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-chart-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-pivot': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-shape': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-shape-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-sparkline': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-sparkline-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/edit-history-viewer': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-chart': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-chart-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-pivot': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-shape': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-shape-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-sparkline': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-sparkline-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/protocol': 0.1.48 - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-conditional-formatting': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-conditional-formatting-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-hyper-link-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-numfmt': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-table': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/protocol': 0.1.49 + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-conditional-formatting': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-conditional-formatting-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-hyper-link-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-numfmt': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-table': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - '@types/react' @@ -19607,31 +19610,31 @@ snapshots: - react - react-dom - '@univerjs-pro/edit-history-viewer@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/edit-history-viewer@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-chart': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-pivot': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-shape': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-sparkline': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-chart': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-pivot': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-shape': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-sparkline': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/protocol': 0.1.48 - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-conditional-formatting': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-table': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/protocol': 0.1.49 + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-conditional-formatting': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-table': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19639,37 +19642,37 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/engine-chart@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/engine-chart@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs-pro/engine-formula@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/engine-formula@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - react - rxjs - '@univerjs-pro/engine-pivot@0.20.0': {} + '@univerjs-pro/engine-pivot@0.20.1': {} - '@univerjs-pro/engine-shape@0.20.0': {} + '@univerjs-pro/engine-shape@0.20.1': {} - '@univerjs-pro/exchange-client@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/exchange-client@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs-pro/collaboration': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/protocol': 0.1.48 - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/protocol': 0.1.49 + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) pako: 2.1.0 react: 19.2.4 rxjs: 7.8.2 @@ -19678,34 +19681,34 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/license@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/license@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: '@noble/ed25519': 2.3.0 '@noble/hashes': 1.8.0 - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs-pro/print@0.20.0': {} + '@univerjs-pro/print@0.20.1': {} - '@univerjs-pro/sheets-chart-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-chart-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/engine-chart': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-chart': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-chart': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-chart': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19713,25 +19716,25 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/sheets-chart@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-chart@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/engine-chart': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-chart': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs-pro/sheets-exchange-client@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-exchange-client@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/exchange-client': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/exchange-client': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -19739,20 +19742,20 @@ snapshots: - react-dom - rxjs - '@univerjs-pro/sheets-pivot-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-pivot-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/engine-pivot': 0.20.0 - '@univerjs-pro/sheets-pivot': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-pivot': 0.20.1 + '@univerjs-pro/sheets-pivot': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19760,35 +19763,35 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/sheets-pivot@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-pivot@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/engine-pivot': 0.20.0 - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-pivot': 0.20.1 + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs-pro/sheets-print@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-print@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration-client': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/print': 0.20.0 - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/print': 0.20.1 + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19796,23 +19799,23 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/sheets-shape-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-shape-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/engine-shape': 0.20.0 - '@univerjs-pro/sheets-shape': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-shape': 0.20.1 + '@univerjs-pro/sheets-shape': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19820,31 +19823,31 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/sheets-shape@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-shape@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/engine-shape': 0.20.0 - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-shape': 0.20.1 + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - react - rxjs - '@univerjs-pro/sheets-sparkline-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-sparkline-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/sheets-sparkline': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-sparkline': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-graphics': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-graphics': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19852,24 +19855,24 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs-pro/sheets-sparkline@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/sheets-sparkline@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs-pro/thread-comment-datasource@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs-pro/thread-comment-datasource@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration-client': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/protocol': 0.1.48 - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/protocol': 0.1.49 + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - '@types/react' @@ -19877,13 +19880,12 @@ snapshots: - react - react-dom - '@univerjs/core@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/core@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: '@univerjs/protocol': 0.1.48 - '@univerjs/themes': 0.20.0 + '@univerjs/themes': 0.20.1 '@wendellhu/redi': 1.1.1(react@19.2.4) async-lock: 1.4.1 - dayjs: 1.11.20 fast-diff: 1.3.0 kdbush: 4.0.2 lodash-es: 4.18.1 @@ -19895,14 +19897,14 @@ snapshots: transitivePeerDependencies: - react - '@univerjs/data-validation@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/data-validation@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/design@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@univerjs/design@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -19913,7 +19915,6 @@ snapshots: '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) class-variance-authority: 0.7.1 clsx: 2.1.1 - dayjs: 1.11.20 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) react-transition-group: 4.4.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -19923,17 +19924,17 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/docs-drawing-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/docs-drawing-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19941,24 +19942,24 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/docs-drawing@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/docs-drawing@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - react - rxjs - '@univerjs/docs-hyper-link-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/docs-hyper-link-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19966,23 +19967,23 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/docs-hyper-link@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/docs-hyper-link@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - react - rxjs - '@univerjs/docs-thread-comment-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/docs-thread-comment-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -19990,15 +19991,15 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/docs-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/docs-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20006,22 +20007,22 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/docs@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/docs@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/drawing-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/drawing-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20029,29 +20030,29 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/drawing@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/drawing@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) ot-json1: 1.0.2 rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/engine-formula@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/engine-formula@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: '@flatten-js/interval-tree': 1.1.3 - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) decimal.js: 10.6.0 rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/engine-render@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/engine-render@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: '@floating-ui/dom': 1.7.6 '@floating-ui/utils': 0.2.11 - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) cjk-regex: 3.4.0 franc-min: 6.2.0 opentype.js: 1.3.4 @@ -20059,13 +20060,13 @@ snapshots: transitivePeerDependencies: - react - '@univerjs/find-replace@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/find-replace@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20078,19 +20079,19 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@univerjs/network@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/network@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/preset-docs-advanced@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-docs-advanced@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/docs-exchange-client': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/docs-print': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/exchange-client': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/docs-exchange-client': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/docs-print': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/exchange-client': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20098,11 +20099,11 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-docs-collaboration@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-docs-collaboration@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20110,15 +20111,15 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-docs-core@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-docs-core@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20126,12 +20127,12 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-docs-drawing@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-docs-drawing@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/docs-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20139,10 +20140,10 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-docs-hyper-link@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-docs-hyper-link@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/docs-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-hyper-link-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-hyper-link-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20150,25 +20151,25 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-docs-node-core@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-docs-node-core@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc-node': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc-node': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/preset-docs-thread-comment@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-docs-thread-comment@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/docs-thread-comment-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-thread-comment-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20176,24 +20177,24 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-advanced@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-advanced@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/engine-chart': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/engine-shape': 0.20.0 - '@univerjs-pro/exchange-client': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/license': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-chart': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-chart-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-exchange-client': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-pivot': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-pivot-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-print': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-shape': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-shape-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-sparkline': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/sheets-sparkline-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-graphics': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-chart': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/engine-shape': 0.20.1 + '@univerjs-pro/exchange-client': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/license': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-chart': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-chart-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-exchange-client': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-pivot': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-pivot-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-print': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-shape': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-shape-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-sparkline': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/sheets-sparkline-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-graphics': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20201,15 +20202,15 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-collaboration@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-collaboration@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs-pro/collaboration': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/collaboration-client-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/edit-history-loader': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/edit-history-viewer': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs-pro/thread-comment-datasource': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-advanced': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/collaboration-client-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/edit-history-loader': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/edit-history-viewer': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs-pro/thread-comment-datasource': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-advanced': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20217,10 +20218,10 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-conditional-formatting@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-conditional-formatting@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/sheets-conditional-formatting': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-conditional-formatting-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-conditional-formatting': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-conditional-formatting-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20228,22 +20229,22 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-core@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-core@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/network': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-numfmt': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-numfmt-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/network': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-numfmt': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-numfmt-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20251,11 +20252,11 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-data-validation@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-data-validation@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20263,13 +20264,13 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-drawing@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-drawing@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/docs-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20277,10 +20278,10 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-filter@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-filter@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/sheets-filter': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20288,10 +20289,10 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-find-replace@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-find-replace@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/find-replace': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-find-replace': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/find-replace': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-find-replace': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20299,10 +20300,10 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-hyper-link@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-hyper-link@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/sheets-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-hyper-link-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-hyper-link-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20310,30 +20311,30 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-node-core@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-node-core@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc-node': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-numfmt': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-sort': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc-node': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-numfmt': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-sort': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/preset-sheets-note@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-note@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/sheets-note': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-note-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-note': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-note-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20341,10 +20342,10 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-sort@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-sort@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/sheets-sort': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-sort-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-sort': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-sort-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20352,10 +20353,10 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-table@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-table@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/sheets-table': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-table-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-table': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-table-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20363,12 +20364,12 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/preset-sheets-thread-comment@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/preset-sheets-thread-comment@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/sheets-thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-thread-comment-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-thread-comment-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20376,30 +20377,30 @@ snapshots: - '@types/react' - '@types/react-dom' - '@univerjs/presets@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/presets@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-docs-advanced': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-docs-collaboration': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-docs-core': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-docs-drawing': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-docs-hyper-link': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-docs-node-core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-docs-thread-comment': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-advanced': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-collaboration': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-conditional-formatting': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-core': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-data-validation': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-drawing': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-filter': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-find-replace': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-hyper-link': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-node-core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-note': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-sort': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-table': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/preset-sheets-thread-comment': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-docs-advanced': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-docs-collaboration': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-docs-core': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-docs-drawing': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-docs-hyper-link': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-docs-node-core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-docs-thread-comment': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-advanced': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-collaboration': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-conditional-formatting': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-core': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-data-validation': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-drawing': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-filter': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-find-replace': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-hyper-link': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-node-core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-note': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-sort': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-table': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/preset-sheets-thread-comment': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) rxjs: 7.8.2 @@ -20409,34 +20410,36 @@ snapshots: '@univerjs/protocol@0.1.48': {} - '@univerjs/rpc-node@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/protocol@0.1.49': {} + + '@univerjs/rpc-node@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/rpc@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/rpc@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-conditional-formatting-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-conditional-formatting-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-conditional-formatting': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-conditional-formatting': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20444,30 +20447,30 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-conditional-formatting@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-conditional-formatting@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-data-validation-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-data-validation-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-numfmt': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-numfmt': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20475,31 +20478,31 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-data-validation@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-data-validation@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/protocol': 0.1.48 - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-drawing-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-drawing-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20507,27 +20510,27 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-drawing@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-drawing@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/drawing': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/drawing': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - react - rxjs - '@univerjs/sheets-filter-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-filter-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-filter': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-filter': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20535,24 +20538,24 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-filter@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-filter@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-find-replace@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-find-replace@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/find-replace': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/find-replace': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - '@types/react' @@ -20560,19 +20563,19 @@ snapshots: - react - react-dom - '@univerjs/sheets-formula-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-formula-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20580,21 +20583,21 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-formula@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-formula@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-graphics@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-graphics@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -20602,21 +20605,21 @@ snapshots: - react-dom - rxjs - '@univerjs/sheets-hyper-link-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-hyper-link-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-data-validation': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-hyper-link': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-data-validation': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-hyper-link': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20624,26 +20627,26 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-hyper-link@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-hyper-link@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-note-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-note-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-note': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-note': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20651,25 +20654,25 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-note@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-note@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-numfmt-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-numfmt-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-numfmt': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-numfmt': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20677,25 +20680,25 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-numfmt@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-numfmt@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-sort-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-sort-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-sort': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-sort': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20703,28 +20706,28 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-sort@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-sort@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - react - rxjs - '@univerjs/sheets-table-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-table-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-formula-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-sort': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-table': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-formula-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-sort': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-table': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20732,27 +20735,27 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-table@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-table@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-thread-comment-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-thread-comment-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20760,29 +20763,29 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets-thread-comment@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-thread-comment@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/sheets-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@univerjs/protocol': 0.1.48 - '@univerjs/sheets': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/telemetry': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/sheets': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/telemetry': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20790,34 +20793,34 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/sheets@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/sheets@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-formula': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-formula': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/protocol': 0.1.48 - '@univerjs/rpc': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/rpc': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/telemetry@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/telemetry@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) transitivePeerDependencies: - react - rxjs - '@univerjs/themes@0.20.0': {} + '@univerjs/themes@0.20.1': {} - '@univerjs/thread-comment-ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/thread-comment-ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/docs-ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/docs-ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/thread-comment': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/ui': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) + '@univerjs/thread-comment': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/ui': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) react: 19.2.4 rxjs: 7.8.2 transitivePeerDependencies: @@ -20825,18 +20828,18 @@ snapshots: - '@types/react-dom' - react-dom - '@univerjs/thread-comment@0.20.0(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/thread-comment@0.20.1(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) rxjs: 7.8.2 transitivePeerDependencies: - react - '@univerjs/ui@0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': + '@univerjs/ui@0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': dependencies: - '@univerjs/core': 0.20.0(react@19.2.4)(rxjs@7.8.2) - '@univerjs/design': 0.20.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@univerjs/engine-render': 0.20.0(react@19.2.4)(rxjs@7.8.2) + '@univerjs/core': 0.20.1(react@19.2.4)(rxjs@7.8.2) + '@univerjs/design': 0.20.1(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@univerjs/engine-render': 0.20.1(react@19.2.4)(rxjs@7.8.2) '@univerjs/icons': 1.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@wendellhu/redi': 1.1.1(react@19.2.4) localforage: 1.10.0 From 0df186ef7db2c18878ecfc0a1f44cb5eb5765eef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 04:07:11 +0000 Subject: [PATCH 188/203] Update dependency dompurify to v3.4.0 [SECURITY] --- apps/client/package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 7cff4e1ed0..ff8fc3b56f 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -49,7 +49,7 @@ "clsx": "2.1.1", "color": "5.0.3", "debounce": "3.0.0", - "dompurify": "3.3.3", + "dompurify": "3.4.0", "draggabilly": "3.0.0", "force-graph": "1.51.2", "i18next": "26.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 37746a1459..32f8497d26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -307,8 +307,8 @@ importers: specifier: 3.0.0 version: 3.0.0 dompurify: - specifier: 3.3.3 - version: 3.3.3 + specifier: 3.4.0 + version: 3.4.0 draggabilly: specifier: 3.0.0 version: 3.0.0 @@ -8080,8 +8080,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.3.3: - resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} + dompurify@3.4.0: + resolution: {integrity: sha512-nolgK9JcaUXMSmW+j1yaSvaEaoXYHwWyGJlkoCTghc97KgGDDSnpoU/PlEnw63Ah+TGKFOyY+X5LnxaWbCSfXg==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -22936,7 +22936,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.3.3: + dompurify@3.4.0: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -25969,7 +25969,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.14 dayjs: 1.11.20 - dompurify: 3.3.3 + dompurify: 3.4.0 katex: 0.16.45 khroma: 2.1.0 lodash-es: 4.18.1 @@ -27583,7 +27583,7 @@ snapshots: classnames: 2.5.1 core-js: 3.46.0 decko: 1.2.0 - dompurify: 3.3.3 + dompurify: 3.4.0 eventemitter3: 5.0.1 json-pointer: 0.6.2 lunr: 2.3.9 From 7e3683b8c262da9c8550df41a9142c53fb2f275a Mon Sep 17 00:00:00 2001 From: contributor Date: Thu, 16 Apr 2026 08:09:34 +0300 Subject: [PATCH 189/203] fix revisions dialog shows translation keys instead of values --- apps/client/src/translations/en/translation.json | 2 +- apps/client/src/widgets/type_widgets/options/other.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index e6c88a6c19..190edb998d 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1341,7 +1341,7 @@ "erase_deleted_notes_now": "Erase deleted notes now", "deleted_notes_erased": "Deleted notes have been erased." }, - "revisions": { + "revisions_snapshot": { "title": "Note Revisions" }, "revisions_snapshot_interval": { diff --git a/apps/client/src/widgets/type_widgets/options/other.tsx b/apps/client/src/widgets/type_widgets/options/other.tsx index 44466cb620..1ab94476c4 100644 --- a/apps/client/src/widgets/type_widgets/options/other.tsx +++ b/apps/client/src/widgets/type_widgets/options/other.tsx @@ -177,7 +177,7 @@ function RevisionSettings() { const [ revisionSnapshotNumberLimit, setRevisionSnapshotNumberLimit ] = useTriliumOption("revisionSnapshotNumberLimit"); return ( - + Date: Thu, 16 Apr 2026 07:47:25 +0200 Subject: [PATCH 190/203] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ --- docs/README-ug.md | 235 ++++++++++++++++++++++++---------------------- 1 file changed, 121 insertions(+), 114 deletions(-) diff --git a/docs/README-ug.md b/docs/README-ug.md index 1dd96d5ed3..e44e6217e6 100644 --- a/docs/README-ug.md +++ b/docs/README-ug.md @@ -14,11 +14,11 @@ ![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran) ![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran)\ ![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/trilium) -![GitHub Downloads (all assets, all -releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\ +![GitHub Downloads (بارلىق ماتېرىياللار، بارلىق تارىخىي +نۇسخىلار)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\ [![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) -[![Translation -status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/) +[![تەرجىمە قىلىنىش +ئەھۋالى](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/) @@ -36,135 +36,142 @@ Trilium Notes بولسا ھەقسىز، ئوچۇق كودلۇق، سىستېما Trilium Screenshot ## چۈشۈرۈش⏬ -- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) – - stable version, recommended for most users. -- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – - unstable development version, updated daily with the latest features and - fixes. +- [ئەڭ يېڭى نۇسخىسى](https://github.com/TriliumNext/Trilium/releases/latest) – + مۇقىم نۇسخا، كۆپ ساندىكى ئابونتلارنىڭ ئىشلىتىشى تەۋسىيە قىلىنىدۇ. +- [كۈندىلىك + قۇرۇلما](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – مۇقىم + بولمىغان ئېچىش نۇسخىسى، ھەر كۈنى يېڭىلىنىدۇ، ئەڭ يېڭى ئىقتىدارلار ۋە + تۈزىتىشلەرنى ئۆز ئىچىگە ئالىدۇ. ## ھۆججەتلەر📚 -**Visit our comprehensive documentation at -[docs.triliumnotes.org](https://docs.triliumnotes.org/)** +بىزنىڭ تولۇق قوللانمىمىزنى كۆرۈڭ: +[docs.triliumnotes.org](https://docs.triliumnotes.org/) -Our documentation is available in multiple formats: -- **Online Documentation**: Browse the full documentation at +بىزنىڭ قوللانمىمىزنىڭ بىر قانچە خىل شەكىلدىكى نۇسخىلىرى بار: +- **تور قوللانمىسى**:بىزنىڭ تولۇق قوللانمىمىزنى مۇشۇ شەكىلدە كۆرۈڭ: [docs.triliumnotes.org](https://docs.triliumnotes.org/) -- **In-App Help**: Press `F1` within Trilium to access the same documentation - directly in the application -- **GitHub**: Navigate through the [User Guide](./User%20Guide/User%20Guide/) in - this repository +- **پروگرامما ئىچىدىكى ياردەم**: Trilium دا` F1` نى باسسىڭىزلا، پروگرامما ئىچىدە + ئوخشاش بىر قوللانمىنى بىۋاسىتە كۆرەلەيسىز +- **GitHub**:بۇ ئامباردىكى [ئابونت قوللانمىسىنى](./User%20Guide/User%20Guide/) + كۆرۈڭ -### Quick Links -- [Getting Started Guide](https://docs.triliumnotes.org/) -- [Installation Instructions](https://docs.triliumnotes.org/user-guide/setup) +### تېز ئۇلانمىلار +- [ئابونت چۈشەندۈرۈشى](https://docs.triliumnotes.org/) +- [قاچىلاش چۈشەندۈرۈشى](https://docs.triliumnotes.org/user-guide/setup) - [Docker - Setup](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker) -- [Upgrading - TriliumNext](https://docs.triliumnotes.org/user-guide/setup/upgrading) -- [Basic Concepts and - Features](https://docs.triliumnotes.org/user-guide/concepts/notes) -- [Patterns of Personal Knowledge - Base](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge) + تەڭشەكلىرى](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker) +- [TriliumNext نى دەرىجىسىنى + ئۆستۈرۈش](https://docs.triliumnotes.org/user-guide/setup/upgrading) +- [ئاساسىي ئۇقۇملار ۋە + ئالاھىدىلىكلەر](https://docs.triliumnotes.org/user-guide/concepts/notes) +- [شەخسىي بىلىم ئامبىرى + ئەندىزىسى](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge) -## 🎁 Features +## 🎁 ئىقتىدارلار -* Notes can be arranged into arbitrarily deep tree. Single note can be placed - into multiple places in the tree (see - [cloning](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning)) -* Rich WYSIWYG note editor including e.g. tables, images and - [math](https://docs.triliumnotes.org/user-guide/note-types/text) with markdown - [autoformat](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting) -* Support for editing [notes with source - code](https://docs.triliumnotes.org/user-guide/note-types/code), including - syntax highlighting -* Fast and easy [navigation between - notes](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation), - full text search and [note - hoisting](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting) -* Seamless [note - versioning](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions) -* Note - [attributes](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes) - can be used for note organization, querying and advanced - [scripting](https://docs.triliumnotes.org/user-guide/scripts) -* UI available in English, German, Spanish, French, Romanian, and Chinese - (simplified and traditional) -* Direct [OpenID and TOTP - integration](https://docs.triliumnotes.org/user-guide/setup/server/mfa) for - more secure login -* [Synchronization](https://docs.triliumnotes.org/user-guide/setup/synchronization) - with self-hosted sync server - * there are [3rd party services for hosting synchronisation - server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) -* [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) - (publishing) notes to public internet -* Strong [note - encryption](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) - with per-note granularity -* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type - "canvas") -* [Relation - maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and - [note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map) - for visualizing notes and their relations -* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/) -* [Geo maps](https://docs.triliumnotes.org/user-guide/collections/geomap) with - location pins and GPX tracks -* [Scripting](https://docs.triliumnotes.org/user-guide/scripts) - see [Advanced - showcases](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases) -* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) for - automation -* Scales well in both usability and performance upwards of 100 000 notes -* Touch optimized [mobile - frontend](https://docs.triliumnotes.org/user-guide/setup/mobile-frontend) for - smartphones and tablets -* Built-in [dark - theme](https://docs.triliumnotes.org/user-guide/concepts/themes), support for - user themes -* [Evernote](https://docs.triliumnotes.org/user-guide/concepts/import-export/evernote) - and [Markdown import & - export](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown) -* [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) for - easy saving of web content -* Customizable UI (sidebar buttons, user-defined widgets, ...) -* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), - along with a Grafana Dashboard. +* خاتىرىلەرنى ھەر قانداق چوڭقۇرلۇقتىكى دەرەخسىمان قۇرۇلمىغا تەشكىللەشكە بولىدۇ. + بىرلا خاتىرىنى دەرەخنىڭ كۆپلىگەن ئورنىغا قويۇشقا بولىدۇ ([خاتىرىنى + كۆپەيتىش/كلونلاش](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning) + غا قاراڭ) +* تۆت تەرەپتىن كۆرۈنىدىغان (WYSIWYG) بېيىتىلغان خاتىرە تەھرىرلىگۈچ، جەدۋەل، + رەسىم ۋە [ماتېماتىكىلىق + فورمۇلا](https://docs.triliumnotes.org/user-guide/note-types/text)نى قوللايدۇ، + ھەمدە Markdown نىڭ [ئاپتوماتىك + فورماتى](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting)غا + ئىگە +* [پروگرامما كودى + خاتىرىسى](https://docs.triliumnotes.org/user-guide/note-types/code)نى + تەھرىرلەشنى قوللايدۇ، كود نۇرىنى ئۆز ئىچىگە ئالىدۇ +* خاتىرىلەر ئارىسىدا تېز ۋە ئوڭاي + [يېتەكلەش](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation)، + پۈتۈن تېكىستلىك ئىزدەش، ھەمدە [خاتىرىگە فوكۇسلىنىش + (hoisting)](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting) +* ئۈزۈلۈپ قالمايدىغان [خاتىرە نۇسخىسىنى + باشقۇرۇش](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions) +* خاتىرە + [خاسلىقى](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes) + خاتىرىلەرنى تەشكىللەش، سۈرۈشتۈرۈش ۋە يۇقىرى دەرىجىلىك + [script](https://docs.triliumnotes.org/user-guide/scripts)لەرگە ئىشلىتىلىدۇ +* UI ئىنگلىزچە، گېرمانچە، ئىسپانچە، فىرانسۇزچە، رۇمىنىيەچە ۋە خەنزۇچە تىللىرىدا + تەمىنلىنىدۇ +* تېخىمۇ بىخەتەر كىرىش ئۈچۈن [OpenID ۋە + TOTP](https://docs.triliumnotes.org/user-guide/setup/server/mfa) بىۋاسىتە بىر + گەۋدىلەشتۈرۈلگەن +* ئۆزى قۇرغان ماسقەدەملەش مۇلازىمىتىرى بىلەن + [ماسقەدەملەش](https://docs.triliumnotes.org/user-guide/setup/synchronization) + * [ماسقەدەملەش مۇلازىمىتىرىنى ئورۇنلاشتۇرۇش ئۈچۈن ئۈچىنچى تەرەپ + مۇلازىمىتى](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting) + بار +* خاتىرىنى ئىنتېرنېت تورىغا + [ھەمبەھرىلەش](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) + (ئاشكارە ئېلان قىلىش +* ھەر بىر خاتىرە بىرلىك قىلىنغان كۈچلۈك [خاتىرە + شىفىرلاش](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) +* قولدا سىزىلغان/ئىزاھلىق رەسىم: [Excalidraw](https://excalidraw.com/) غا + ئاساسلانغان (خاتىرە تىپى «canvas») +* خاتىرە ۋە ئۇلارنىڭ مۇناسىۋىتىنى كۆرسىتىپ بېرىدىغان [مۇناسىۋەت + خەرىتىسى](https://docs.triliumnotes.org/user-guide/note-types/relation-map) ۋە + [خاتىرە/ئۇلانما + خەرىتىسى](https://docs.triliumnotes.org/user-guide/note-types/note-map) +* تەپەككۇر خەرىتىسى: [Mind Elixir](https://docs.mind-elixir.com/) غا ئاساسلانغان +* ئورۇن بەلگىلەش بەلگىسى ۋە GPX يۆنىلىش لىنىيىسى بولغان [جۇغراپىيىلىك + خەرىتە](https://docs.triliumnotes.org/user-guide/collections/geomap) +* [Script](https://docs.triliumnotes.org/user-guide/scripts) - [ئىلغار + كۆرسىتىش](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases)گە + قاراڭ +* ئاپتوماتلاشتۇرۇش ئۈچۈن ئىشلىتىلىدىغان [REST + API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) +* ئىشلىتىشچانلىقى ۋە ئۈنۈمى جەھەتتە ياخشى كېڭىيىشچانلىققا ئىگە، 100,000 دىن + ئارتۇق خاتىرىنى قوللايدۇ +* يانفون ۋە تاختا كومپيۇتېر ئۈچۈن ئەلالاشتۇرۇلغان [كۆچمە + تەرەپ](https://docs.triliumnotes.org/user-guide/setup/mobile-frontend) +* ئىچىگە ئورۇنلاشتۇرۇلغان [قېنىق رەڭلىك + تېما](https://docs.triliumnotes.org/user-guide/concepts/themes) +* [Evernote دىن + ئەكىرىش](https://docs.triliumnotes.org/user-guide/concepts/import-export/evernote) + ۋە [Markdown نى ئەكىرىش ھەم + چىقىرىش](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown) +* توردىكى مەزمۇنلارنى تېز ساقلاشقا ئىشلىتىلىدىغان [Web + Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) +* ئىختىيارىي تەڭشىگىلى بولىدىغان UI (يان تىزىملىك كۇنۇپكىلىرى، ئىشلەتكۈچى ئۆزى + بەلگىلىگەن كىچىك قوراللار قاتارلىقلار) +* [Metrics ](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics)، + شۇنداقلا Grafana كۆرسىتىش تاختىسى. -✨ Check out the following third-party resources/communities for more TriliumNext -related goodies: +✨ تېخىمۇ كۆپ TriliumNext تېمىلىرى، قوليازما، قىستۇرما ۋە مەنبەلەرگە ئېرىشمەكچى +بولسىڭىز، تۆۋەندىكى ئۈچىنچى تەرەپ مەنبەلىرى ياكى جەمئىيەتلىرىدىن پايدىلانسىڭىز +بولىدۇ: -- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party - themes, scripts, plugins and more. -- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more. +- [awesome-trilium](https://github.com/Nriver/awesome-trilium) (ئۈچىنچى تەرەپ + تېمىلىرى، قوليازمىلار، قىستۇرمىلار ۋە باشقىلار). +- [TriliumRocks!](https://trilium.rocks/) (دەرسلىكلەر، يېتەكچىلەر ۋە باشقىلار). -## ❓Why TriliumNext? +## ⚠️ نېمە ئۈچۈن TriliumNext نى تاللايمىز؟ -The original Trilium developer ([Zadam](https://github.com/zadam)) has -graciously given the Trilium repository to the community project which resides -at https://github.com/TriliumNext +Trilium نىڭ ئەسلى ئىجادچىسى ([Zadam](https://github.com/zadam)) سېخىيلىق بىلەن +Trilium كود ئامبىرىنى جەمئىيەت تۈرىگە ئۆتكۈزۈپ بەردى، بۇ تۈر ھازىر مۇشۇ ئادرېستا +ساقلىنىۋاتىدۇ: https://github.com/TriliumNext -### ⬆️Migrating from Zadam/Trilium? +### ⬆️ Trilium دىن كۆچۈرۈش؟ -There are no special migration steps to migrate from a zadam/Trilium instance to -a TriliumNext/Trilium instance. Simply [install -TriliumNext/Trilium](#-installation) as usual and it will use your existing -database. +zadam/Trilium نۇسخىسىدىن TriliumNext/Notes غا كۆچۈش ئۈچۈن ئالاھىدە كۆچۈرۈش +باسقۇچلىرى كېرەك ئەمەس. [TriliumNext/Notes نى ئادەتتىكى ئۇسۇلدا +قاچىلىسىڭىزلا](#-installation)(#-install)، ئۇ بىۋاسىتە ھازىرقى ساندانىڭىزنى +ئىشلىتىدۇ. -Versions up to and including -[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are -compatible with the latest zadam/trilium version of -[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later -versions of TriliumNext/Trilium have their sync versions incremented which -prevents direct migration. +[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) غىچە +بولغان نەشرلىرى zadam/trilium نىڭ ئەڭ يېڭى +[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7) نەشرى بىلەن +ماسلىشىدۇ. ئۇنىڭدىن كېيىنكى TriliumNext نەشرلىرىدە ماسقەدەملەش نەشر نومۇرى +ئۆستۈرۈلگەن بولۇپ، يۇقىرىقىلار بىلەن ماسلاشمايدۇ. -## 💬 Discuss with us +## 💬 بىز بىلەن پىكىر ئالماشتۇرۇڭ -Feel free to join our official conversations. We would love to hear what -features, suggestions, or issues you may have! +رەسمىي جەمئىيىتىمىزگە كەلگەنلىكىڭىزنى قارشى ئالىمىز. بىز سىزنىڭ ئىقتىدار، تەكلىپ +ياكى مەسىلىلەر ھەققىدىكى پىكىرلىرىڭىزنى ئاڭلاشنى ئارزۇ قىلىمىز! -- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous - discussions.) +- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (ماسقەدەملىك مۇنازىرە) - The `General` Matrix room is also bridged to [XMPP](xmpp:discuss@trilium.thisgreat.party?join) - [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For From 2fc907e7568fcd3037d170328194f9442e27453c Mon Sep 17 00:00:00 2001 From: Ulices Date: Thu, 16 Apr 2026 01:06:32 +0200 Subject: [PATCH 191/203] Translated using Weblate (Spanish) Currently translated at 85.3% (1649 of 1932 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/ --- .../src/translations/es/translation.json | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/client/src/translations/es/translation.json b/apps/client/src/translations/es/translation.json index f3588aac03..2f26db1ba8 100644 --- a/apps/client/src/translations/es/translation.json +++ b/apps/client/src/translations/es/translation.json @@ -428,7 +428,9 @@ "print_landscape": "Al exportar a PDF, cambia la orientación de la página a paisaje en lugar de retrato.", "print_page_size": "Al exportar a PDF, cambia el tamaño de la página. Valores soportados: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", "color_type": "Color", - "textarea": "Texto multilínea" + "textarea": "Texto multilínea", + "print_scale": "Al exportar a PDF, cambia la escala del contenido renderizado. Los valores varían de 0.1 (10%) a 2 (200%), por defecto es 1 (100%).", + "print_margins": "Al exportar a PDF, establece márgenes de página. Use default, none, minimum, o valores personalizados como top,right,bottom,left en milímetros." }, "attribute_editor": { "help_text_body1": "Para agregar una etiqueta, simplemente escriba, por ejemplo. #rock o si desea agregar también valor, p.e. #año = 2020", @@ -691,7 +693,11 @@ "export_as_image_png": "PNG (ráster)", "export_as_image_svg": "SVG (vectorial)", "note_map": "Mapa de la nota", - "view_ocr_text": "Ver texto OCR" + "view_ocr_text": "Ver texto OCR", + "word_wrap_auto": "Automático", + "word_wrap_auto_description": "Seguir el ajuste global", + "word_wrap_on": "Encendido", + "word_wrap_off": "Apagado" }, "onclick_button": { "no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido" @@ -792,7 +798,10 @@ "hide_child_notes": "Ocultar subnotas en el árbol", "open_all_in_tabs": "Abrir todo", "open_all_in_tabs_tooltip": "Abrir todos los resultados en nuevas pestañas", - "open_all_confirm": "Esto abrirá {{count}} notas en nuevas pestañas. ¿Continuar?" + "open_all_confirm": "Esto abrirá {{count}} notas en nuevas pestañas. ¿Continuar?", + "open_all_too_one": " ", + "open_all_too_many": " ", + "open_all_too_other": " " }, "edited_notes": { "no_edited_notes_found": "Aún no hay notas editadas en este día...", @@ -1122,7 +1131,7 @@ "note_tree_font": "Fuente del árbol de notas", "note_detail_font": "Fuente de detalle de nota", "monospace_font": "Fuente Monospace (código)", - "not_all_fonts_available": "Es posible que no todas las fuentes enumeradas estén disponibles en su sistema.", + "not_all_fonts_available": "Es posible que no todas las fuentes enumeradas estén disponibles en su sistema", "generic-fonts": "Fuentes genéricas", "sans-serif-system-fonts": "Fuentes Sans-serif del sistema", "serif-system-fonts": "Fuentes Serif del sistema", @@ -1208,7 +1217,7 @@ "attachment_erasure_timeout": { "attachment_erasure_timeout": "Tiempo de espera para borrar archivos adjuntos", "erase_attachments_after": "Borrar archivos adjuntos después de:", - "manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):", + "manual_erasing_description": "Activar el borrado manualmente, ignorando el tiempo de espera definido anteriormente.", "erase_unused_attachments_now": "Borrar ahora los archivos adjuntos no utilizados en la nota", "unused_attachments_erased": "Los archivos adjuntos no utilizados se han eliminado." }, @@ -1219,7 +1228,7 @@ "note_erasure_timeout": { "note_erasure_timeout_title": "Tiempo de espera de borrado de notas", "erase_notes_after": "Borrar notas después de:", - "manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):", + "manual_erasing_description": "Activar el borrado manualmente, ignorando el tiempo de espera definido anteriormente.", "erase_deleted_notes_now": "Borrar notas eliminadas ahora", "deleted_notes_erased": "Las notas eliminadas han sido borradas." }, @@ -1238,7 +1247,7 @@ }, "search_engine": { "title": "Motor de búsqueda", - "custom_search_engine_info": "El motor de búsqueda personalizado requiere que se establezcan un nombre y una URL. Si alguno de estos no está configurado, DuckDuckGo se utilizará como motor de búsqueda predeterminado.", + "custom_search_engine_info": "Utilizado al buscar la web para el texto seleccionado. Si no está configurado, DuckDuckGo será utilizado.", "predefined_templates_label": "Plantillas de motor de búsqueda predefinidas", "bing": "Bing", "baidu": "Baidu", @@ -1613,7 +1622,7 @@ "note_detail": { "could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'", "printing": "Impresión en curso...", - "printing_pdf": "Exportando a PDF en curso..", + "printing_pdf": "Preparando vista preliminar de impresión...", "print_report_collection_content_one": "{{count}} nota en la colección no se puede imprimir porque no son compatibles o está protegida.", "print_report_collection_content_many": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.", "print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.", @@ -1758,7 +1767,7 @@ "title": "Fijo", "description": "las herramientas de edición aparecen en la pestaña de la cinta \"Formato\")." }, - "multiline-toolbar": "Mostrar la barra de herramientas en múltiples líneas si no cabe." + "multiline-toolbar": "Mostrar la barra de herramientas en múltiples líneas si no cabe" } }, "electron_context_menu": { From 8b202b676027d89a2c331b6c03bacccd24d2f446 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Thu, 16 Apr 2026 07:47:50 +0200 Subject: [PATCH 192/203] Translated using Weblate (Uyghur) Currently translated at 14.1% (57 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ug/ --- .../src/assets/translations/ug/server.json | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/ug/server.json b/apps/server/src/assets/translations/ug/server.json index ffc7858ad1..d97651fe0d 100644 --- a/apps/server/src/assets/translations/ug/server.json +++ b/apps/server/src/assets/translations/ug/server.json @@ -5,6 +5,57 @@ "open-jump-to-note-dialog": "\"خاتىرىگە تېز ئۆتۈش\" كۆزنىكىنى ئېچىش", "open-command-palette": "بۇيرۇق تىزىملىكىنى ئېچىش", "quick-search": "تېز ئىزدەشنى قوزغىتىش", - "search-in-subtree": "ھازىرقى خاتىرىنىڭ تارماقلىرىدىن ئىزدەش" + "search-in-subtree": "ھازىرقى خاتىرىنىڭ تارماقلىرىدىن ئىزدەش", + "scroll-to-active-note": "خاتىرە دەرىخىنى نۆۋەتتىكى خاتىرىگە سىيرىش", + "expand-subtree": "نۆۋەتتىكى خاتىرىنىڭ تارماق دەرىخىنى يېيىش", + "collapse-tree": "تولۇق خاتىرە دەرىخىنى يىغىش", + "collapse-subtree": "نۆۋەتتىكى خاتىرىنىڭ تارماق دەرىخىنى يىغىش", + "sort-child-notes": "تارماق خاتىرىلەرنى تەرتىپلەش", + "creating-and-moving-notes": "خاتىرە قۇرۇش ۋە يۆتكەش", + "create-note-after": "نۆۋەتتىكى خاتىرىنىڭ كەينىگە خاتىرە قۇرۇش", + "create-note-into": "نۆۋەتتىكى خاتىرىگە تارماق خاتىرە قۇرۇش", + "create-note-into-inbox": "Inbox ساندۇقى (ئەگەر بېكىتىلگەن بولسا) ياكى كۈندىلىك خاتىرىدە خاتىرە قۇرۇش", + "delete-note": "خاتىرىنى ئۆچۈرۈش", + "move-note-up": "خاتىرىنى يۇقىرىغا يۆتكەش", + "move-note-down": "خاتىرىنى تۆۋەنگە يۆتكەش", + "move-note-up-in-hierarchy": "خاتىرىنى قاتلام بويىچە يۇقىرىغا يۆتكەش", + "move-note-down-in-hierarchy": "خاتىرىنى قاتلام بويىچە تۆۋەنگە يۆتكەش", + "edit-note-title": "دەرەخسىمان قۇرۇلمىدىن خاتىرە تەپسىلاتىغا سەكرەش ۋە تېمىنى تەھرىرلەش", + "edit-branch-prefix": "تارماق ئالدى قوشۇلغۇچىنى تەھرىرلەش سۆزلىشىش كۆزنىكىنى كۆرسىتىش", + "clone-notes-to": "تاللانغان خاتىرىنى كۆچۈرۈپ قويۇش ئورنى", + "move-notes-to": "تاللانغان خاتىرىنى يۆتكەش ئورنى", + "note-clipboard": "خاتىرە چاپلاش تاختىسى", + "copy-notes-to-clipboard": "تاللانغان خاتىرىنى چاپلاش تاختىسىغا كۆچۈرۈش", + "paste-notes-from-clipboard": "چاپلاش تاختىسىدىكى خاتىرىنى نۆۋەتتىكى خاتىرىگە چاپلاش", + "cut-notes-to-clipboard": "تاللانغان خاتىرىنى چاپلاش تاختىسىغا كېسىپ ئېلىش", + "select-all-notes-in-parent": "نۆۋەتتىكى خاتىرە دەرىجىسىدىكى بارلىق خاتىرىلەرنى تاللاش", + "add-note-above-to-the-selection": "ئۈستىدىكى خاتىرىنى تاللانمىغا قوشۇش", + "add-note-below-to-selection": "ئاستىدىكى خاتىرىنى تاللانمىغا قوشۇش", + "duplicate-subtree": "تارماق دەرەخنى كۆچۈرۈش", + "tabs-and-windows": "بەتكۈچ ۋە كۆزنەك", + "open-new-tab": "يېڭى بەتكۈچ ئېچىش", + "close-active-tab": "نۆۋەتتىكى بەتكۈچنى يېپىش", + "reopen-last-tab": "ئەڭ ئاخىرقى يېپىلغان بەتكۈچنى قايتا ئېچىش", + "activate-next-tab": "كېيىنكى بەتكۈچكە ئالماشتۇرۇش", + "activate-previous-tab": "ئالدىنقى بەتكۈچكە ئالماشتۇرۇش", + "open-new-window": "يېڭى كۆزنەك ئېچىش", + "toggle-tray": "سىستېما تەخسىسى سىنبەلگىسىنى كۆرسىتىش/يوشۇرۇش", + "first-tab": "بىرىنچى بەتكۈچكە ئالماشتۇرۇش", + "second-tab": "ئىككىنچى بەتكۈچكە ئالماشتۇرۇش", + "third-tab": "ئۈچىنچى بەتكۈچكە ئالماشتۇرۇش", + "fourth-tab": "تۆتىنچى بەتكۈچكە ئالماشتۇرۇش", + "fifth-tab": "بەشىنچى بەتكۈچكە ئالماشتۇرۇش", + "sixth-tab": "ئالتىنچى بەتكۈچكە ئالماشتۇرۇش", + "seventh-tab": "يەتتىنچى بەتكۈچكە ئالماشتۇرۇش", + "eight-tab": "سەككىزىنچى بەتكۈچكە ئالماشتۇرۇش", + "ninth-tab": "توققۇزىنچى بەتكۈچكە ئالماشتۇرۇش", + "last-tab": "ئەڭ ئاخىرقى بەتكۈچكە ئالماشتۇرۇش", + "dialogs": "سۆھبەت رامكىلىرى", + "show-note-source": "خاتىرە ئەسلى كودى سۆھبەت رامكىسىنى كۆرسىتىش", + "show-options": "تاللانما سۆھبەت رامكىسىنى كۆرسىتىش", + "show-revisions": "تۈزىتىش تارىخى سۆھبەت رامكىسىنى كۆرسىتىش", + "show-recent-changes": "يېقىنقى ئۆزگىرىشلەر سۆھبەت رامكىسىنى كۆرسىتىش", + "show-sql-console": "SQL كونترول سۇپىسى سۆھبەت رامكىسىنى كۆرسىتىش", + "show-backend-log": "ئارقا سۇپا خاتىرىسى سۆھبەت رامكىسىنى كۆرسىتىش" } } From 062d0863a1dbb949da06b2dc2046172321040570 Mon Sep 17 00:00:00 2001 From: green Date: Thu, 16 Apr 2026 02:51:05 +0200 Subject: [PATCH 193/203] Translated using Weblate (Japanese) Currently translated at 99.9% (1931 of 1932 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- .../src/translations/ja/translation.json | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 5884ece190..3f8408c640 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -491,7 +491,12 @@ "export_as_image": "画像としてエクスポート", "export_as_image_png": "PNG (raster)", "export_as_image_svg": "SVG (vector)", - "view_ocr_text": "OCR テキストを表示" + "view_ocr_text": "OCR テキストを表示", + "word_wrap": "行の折り返し", + "word_wrap_auto": "自動", + "word_wrap_auto_description": "グローバル設定に従う", + "word_wrap_on": "オン", + "word_wrap_off": "オフ" }, "command_palette": { "export_note_title": "ノートをエクスポート", @@ -1850,7 +1855,7 @@ "note_detail": { "could_not_find_typewidget": "タイプ {{type}} の typeWidget が見つかりませんでした", "printing": "印刷中です...", - "printing_pdf": "PDF へのエクスポート中です...", + "printing_pdf": "印刷プレビューを準備中...", "print_report_title": "レポートを印刷", "print_report_collection_content_other": "コレクション内の {{count}} 件のノートは、サポートされていないか保護されているため、印刷できませんでした。", "print_report_collection_details_button": "詳細を見る", @@ -2141,7 +2146,7 @@ "tab_width_title": "タブ幅を変更", "tab_width_spaces": "{{count}} スペース", "tab_width_spaces_short": "スペース: {{width}}", - "tab_width_tabs": "タブ ({{width}})", + "tab_width_tabs": "タブ: {{width}}", "tab_width_use_default": "デフォルトを使用 ({{width}})", "tab_width_use_default_style": "デフォルトを使用 ({{style}})", "tab_width_display_header": "表示幅", @@ -2436,6 +2441,13 @@ "page_ranges": "ページ", "page_ranges_hint": "全ページを印刷する場合は空欄のままにしてください。", "page_ranges_invalid": "無効な形式です。例: 1-5, 8, 11-13 のように指定してください。", - "page_ranges_placeholder": "例: 1-5, 8, 11-13" + "page_ranges_placeholder": "例: 1-5, 8, 11-13", + "print": "印刷", + "export_pdf": "PDF としてエクスポート", + "system_print": "システムダイアログを使用して印刷", + "destination": "出力先", + "destination_pdf": "PDF として保存", + "destination_printers": "プリンター", + "destination_default": "デフォルト" } } From 1154a1f7bfdd095502ff1ed897c48467b9225385 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Thu, 16 Apr 2026 07:48:45 +0200 Subject: [PATCH 194/203] Translated using Weblate (Uyghur) Currently translated at 51.2% (61 of 119 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ug/ --- docs/README-ug.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README-ug.md b/docs/README-ug.md index e44e6217e6..ed99cc583d 100644 --- a/docs/README-ug.md +++ b/docs/README-ug.md @@ -172,8 +172,8 @@ zadam/Trilium نۇسخىسىدىن TriliumNext/Notes غا كۆچۈش ئۈچۈن ياكى مەسىلىلەر ھەققىدىكى پىكىرلىرىڭىزنى ئاڭلاشنى ئارزۇ قىلىمىز! - [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (ماسقەدەملىك مۇنازىرە) - - The `General` Matrix room is also bridged to - [XMPP](xmpp:discuss@trilium.thisgreat.party?join) + - `General `Matrix ئۆيىمۇ [XMPP](xmpp:discuss@trilium.thisgreat.party?join) غا + ئۇلانغان - [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For asynchronous discussions.) - [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug From 0e76f412ec43e78fe87b9c0246335ad52ad02a20 Mon Sep 17 00:00:00 2001 From: passkal4 Date: Thu, 16 Apr 2026 07:49:14 +0200 Subject: [PATCH 195/203] Translated using Weblate (Uyghur) Currently translated at 14.3% (58 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ug/ --- apps/server/src/assets/translations/ug/server.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/ug/server.json b/apps/server/src/assets/translations/ug/server.json index d97651fe0d..593275bcb8 100644 --- a/apps/server/src/assets/translations/ug/server.json +++ b/apps/server/src/assets/translations/ug/server.json @@ -56,6 +56,7 @@ "show-revisions": "تۈزىتىش تارىخى سۆھبەت رامكىسىنى كۆرسىتىش", "show-recent-changes": "يېقىنقى ئۆزگىرىشلەر سۆھبەت رامكىسىنى كۆرسىتىش", "show-sql-console": "SQL كونترول سۇپىسى سۆھبەت رامكىسىنى كۆرسىتىش", - "show-backend-log": "ئارقا سۇپا خاتىرىسى سۆھبەت رامكىسىنى كۆرسىتىش" + "show-backend-log": "ئارقا سۇپا خاتىرىسى سۆھبەت رامكىسىنى كۆرسىتىش", + "show-help": "ياردەمنى كۆرسىتىش" } } From 093e671fb5e54ebda4ae76bf5524c9d4a2147fea Mon Sep 17 00:00:00 2001 From: passkal4 Date: Thu, 16 Apr 2026 07:49:44 +0200 Subject: [PATCH 196/203] Translated using Weblate (Uyghur) Currently translated at 52.1% (62 of 119 strings) Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ug/ --- docs/README-ug.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README-ug.md b/docs/README-ug.md index ed99cc583d..0c4c59b054 100644 --- a/docs/README-ug.md +++ b/docs/README-ug.md @@ -174,8 +174,8 @@ zadam/Trilium نۇسخىسىدىن TriliumNext/Notes غا كۆچۈش ئۈچۈن - [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (ماسقەدەملىك مۇنازىرە) - `General `Matrix ئۆيىمۇ [XMPP](xmpp:discuss@trilium.thisgreat.party?join) غا ئۇلانغان -- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For - asynchronous discussions.) +- [GitHub Discussions](https://github.com/TriliumNext/Trilium/discussions) + (ئاسىنكرون مۇنازىرە). - [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug reports and feature requests.) From c36264203f9f008009099aa19382b187baa58744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Thu, 16 Apr 2026 12:10:27 +0200 Subject: [PATCH 197/203] Translated using Weblate (Irish) Currently translated at 100.0% (403 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ga/ --- apps/server/src/assets/translations/ga/server.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/server/src/assets/translations/ga/server.json b/apps/server/src/assets/translations/ga/server.json index 00e9c2ef9f..d5dc6d23ca 100644 --- a/apps/server/src/assets/translations/ga/server.json +++ b/apps/server/src/assets/translations/ga/server.json @@ -458,5 +458,8 @@ "fulltext-after-expression": "Ní abairt bhailí í \"{{- token}}\". Chun téacs a chuardach, cuir é roimh scagairí tréithe (m.sh., \"{{- token}} #label\" in ionad \"#label {{- token}}\").", "unrecognized-expression": "Sloinneadh neamhaitheanta \"{{- token}}\"" } + }, + "password": { + "incorrect": "Tá an focal faire a chuir tú isteach mícheart." } } From cd6f63a908748224b68f79fa352562d265e680e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aindri=C3=BA=20Mac=20Giolla=20Eoin?= Date: Thu, 16 Apr 2026 12:34:56 +0200 Subject: [PATCH 198/203] Translated using Weblate (Irish) Currently translated at 99.9% (1931 of 1932 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/ --- .../src/translations/ga/translation.json | 295 ++++++++++++------ 1 file changed, 205 insertions(+), 90 deletions(-) diff --git a/apps/client/src/translations/ga/translation.json b/apps/client/src/translations/ga/translation.json index a6facbbf36..7326807d26 100644 --- a/apps/client/src/translations/ga/translation.json +++ b/apps/client/src/translations/ga/translation.json @@ -461,7 +461,9 @@ "print_landscape": "Agus é á onnmhairiú go PDF, athraítear treoshuíomh an leathanaigh go tírdhreach seachas portráid.", "print_page_size": "Agus é á easpórtáil go PDF, athraítear méid an leathanaigh. Luachanna tacaithe: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", "color_type": "Dath", - "textarea": "Téacs Il-líne" + "textarea": "Téacs Il-líne", + "print_scale": "Agus é á onnmhairiú go PDF, athraítear scála an ábhair rindreáilte. Tá na luachanna idir 0.1 (10%) agus 2 (200%), is é 1 (100%) an réamhshocrú.", + "print_margins": "Agus é á onnmhairiú go PDF, socraítear corrlaigh leathanaigh. Úsáid luachanna réamhshocraithe, gan aon cheann, íosmhéid, nó luachanna saincheaptha mar barr, deas, bun, clé i milliméadair." }, "attribute_editor": { "help_text_body1": "Chun lipéad a chur leis, clóscríobh m.sh. #rock nó más mian leat luach a chur leis freisin ansin m.sh. #year = 2020", @@ -693,7 +695,12 @@ "export_as_image_png": "PNG (rastar)", "export_as_image_svg": "SVG (veicteoir)", "note_map": "Léarscáil nótaí", - "view_ocr_text": "Féach ar théacs OCR" + "view_ocr_text": "Féach ar théacs OCR", + "word_wrap": "Timfhill focal", + "word_wrap_auto": "Uathoibríoch", + "word_wrap_auto_description": "Lean an suíomh domhanda", + "word_wrap_on": "Ar", + "word_wrap_off": "As" }, "onclick_button": { "no_click_handler": "Níl aon láimhseálaí cliceáil sainithe ag an ngiuirléid cnaipe '{{componentId}}'" @@ -1068,15 +1075,17 @@ "title": "Seiceálacha Comhsheasmhachta", "find_and_fix_button": "Fadhbanna comhsheasmhachta a aimsiú agus a shocrú", "finding_and_fixing_message": "Fadhbanna comhsheasmhachta a aimsiú agus a shocrú...", - "issues_fixed_message": "Tá aon fhadhb chomhsheasmhachta a d'fhéadfadh a bheith aimsithe socraithe anois." + "issues_fixed_message": "Tá aon fhadhb chomhsheasmhachta a d'fhéadfadh a bheith aimsithe socraithe anois.", + "find_and_fix_label": "Fadhbanna comhsheasmhachta a aimsiú agus a shocrú", + "find_and_fix_description": "Déan scanadh le haghaidh aon fhadhbanna comhsheasmhachta sonraí sa bhunachar sonraí agus déan iad a dheisiú go huathoibríoch." }, "database_anonymization": { "title": "Anaithnidiú Bunachar Sonraí", - "full_anonymization": "Anaithnidiú Iomlán", - "full_anonymization_description": "Cruthóidh an gníomh seo cóip nua den bhunachar sonraí agus déanfaidh sé anaithnidiú air (bainfear gach ábhar nótaí agus fágfar struchtúr agus roinnt meiteashonraí neamhíogaire amháin) le go mbeidh sé in ann é a roinnt ar líne chun críocha dífhabhtaithe gan eagla go sceithfidh tú do shonraí pearsanta.", + "full_anonymization": "Anaithnidiú iomlán", + "full_anonymization_description": "Cruthaíonn sé cóip den bhunachar sonraí agus gach ábhar nótaí bainte, rud a fhágann struchtúr agus meiteashonraí neamhíogaire amháin. Sábháilte le roinnt ar líne agus fadhbanna dífhabhtaithe á ndífhabhtú.", "save_fully_anonymized_database": "Sábháil bunachar sonraí lán-anaithnid", - "light_anonymization": "Anaithnidiú Éadrom", - "light_anonymization_description": "Cruthóidh an gníomh seo cóip nua den bhunachar sonraí agus déanfaidh sé beagán anaithnidithe air — go sonrach ní bhainfear ach ábhar na nótaí go léir, ach fanfaidh teidil agus tréithe. Ina theannta sin, fanfaidh nótaí scripte tosaigh/cúil JS saincheaptha agus giuirléidí saincheaptha. Soláthraíonn sé seo níos mó comhthéacs chun na fadhbanna a dhífhabhtú.", + "light_anonymization": "Anaithnidiú éadrom", + "light_anonymization_description": "Cruthaíonn sé cóip agus ábhar na nótaí bainte de, ach fanann teidil, tréithe, agus scripteanna/giuirléidí saincheaptha ann. Soláthraíonn sé níos mó comhthéacs le haghaidh dífhabhtaithe.", "choose_anonymization": "Is féidir leat cinneadh a dhéanamh duit féin an mian leat bunachar sonraí atá anaithnid go hiomlán nó beagán gan ainm a sholáthar. Tá fiú bunachar sonraí atá anaithnid go hiomlán an-úsáideach, ach i gcásanna áirithe is féidir le bunachar sonraí atá anaithnid go héadrom an próiseas chun fabhtanna a aithint agus a shocrú a bhrostú.", "save_lightly_anonymized_database": "Sábháil bunachar sonraí atá anaithnidithe go héadrom", "existing_anonymized_databases": "Bunachair shonraí gan ainm atá ann cheana féin", @@ -1085,14 +1094,17 @@ "error_creating_anonymized_database": "Níorbh fhéidir bunachar sonraí gan ainm a chruthú, seiceáil logaí an chúltaca le haghaidh sonraí", "successfully_created_fully_anonymized_database": "Cruthaíodh bunachar sonraí lán-anaithnid i {{anonymizedFilePath}}", "successfully_created_lightly_anonymized_database": "Cruthaíodh bunachar sonraí atá beagán anaithnid i {{anonymizedFilePath}}", - "no_anonymized_database_yet": "Gan aon bhunachar sonraí anaithnidithe go fóill." + "no_anonymized_database_yet": "Gan aon bhunachar sonraí anaithnidithe go fóill.", + "description": "Cruthaigh cóip gan ainm de do bhunachar sonraí le roinnt le forbróirí agus fadhbanna á ndífhabhtú, gan sonraí pearsanta a nochtadh." }, "database_integrity_check": { "title": "Seiceáil Ionracais Bunachar Sonraí", "check_button": "Seiceáil sláine an bhunachair shonraí", "checking_integrity": "Ag seiceáil sláine an bhunachair shonraí...", "integrity_check_succeeded": "D’éirigh leis an tseiceáil ionracais - níor aimsíodh aon fhadhbanna.", - "integrity_check_failed": "Theip ar an tseiceáil ionracais: {{results}}" + "integrity_check_failed": "Theip ar an tseiceáil ionracais: {{results}}", + "check_integrity_label": "Seiceáil sláine an bhunachair shonraí", + "check_integrity_description": "Fíoraigh nach bhfuil an bunachar sonraí truaillithe ar leibhéal SQLite." }, "sync": { "title": "Sioncrónaigh", @@ -1102,14 +1114,20 @@ "filling_entity_changes": "Líonadh sraitheanna athruithe eintiteas...", "sync_rows_filled_successfully": "Líontar na sraitheanna sioncrónaithe go rathúil", "finished-successfully": "Críochnaíodh an sioncrónú go rathúil.", - "failed": "Theip ar an sioncrónú: {{message}}" + "failed": "Theip ar an sioncrónú: {{message}}", + "force_full_sync_label": "Fórsaigh sioncrónú iomlán", + "force_full_sync_description": "Spreag sioncrónú iomlán leis an bhfreastalaí sioncrónaithe, ag ath-uaslódáil na hathruithe go léir.", + "fill_entity_changes_label": "Athruithe eintitis líonta", + "fill_entity_changes_description": "Athchruthaigh taifid athraithe eintitis. Bain úsáid as seo má tá roinnt athruithe ar iarraidh sa sioncrónú." }, "vacuum_database": { "title": "Bunachar Sonraí Folúis", "description": "Déanfaidh sé seo an bunachar sonraí a atógáil agus de ghnáth beidh comhad bunachar sonraí níos lú mar thoradh air. Ní athrófar aon sonraí i ndáiríre.", "button_text": "Bunachar sonraí folúis", "vacuuming_database": "Bunachar sonraí folúsghlanadh...", - "database_vacuumed": "Tá an bunachar sonraí folúsghlanaithe" + "database_vacuumed": "Tá an bunachar sonraí folúsghlanaithe", + "vacuum_label": "Bunachar sonraí folúis", + "vacuum_description": "Athchruthaigh an bunachar sonraí chun méid an chomhaid a laghdú. Ní athrófar aon sonraí." }, "experimental_features": { "title": "Roghanna Turgnamhacha", @@ -1122,13 +1140,13 @@ "fonts": { "theme_defined": "Téama sainmhínithe", "fonts": "Clónna", - "main_font": "Príomhchló", + "main_font": "Téacs comhéadain", "font_family": "Teaghlach clónna", "size": "Méid", - "note_tree_font": "Cló Crann Nótaí", - "note_detail_font": "Cló Sonraí Nóta", - "monospace_font": "Cló Aonspáis (cód)", - "not_all_fonts_available": "B’fhéidir nach bhfuil na clónna uile atá liostaithe ar fáil ar do chóras.", + "note_tree_font": "Téacs crann nótaí", + "note_detail_font": "Téacs an doiciméid", + "monospace_font": "Téacs aonspáis", + "not_all_fonts_available": "B’fhéidir nach bhfuil na clónna uile atá liostaithe ar fáil ar do chóras", "generic-fonts": "Clónna ginearálta", "sans-serif-system-fonts": "Clónna córais Sans-serif", "serif-system-fonts": "Clónna córais Serif", @@ -1137,7 +1155,12 @@ "serif": "Serif", "sans-serif": "Sans Serif", "monospace": "Aonspás", - "system-default": "Réamhshocrú an chórais" + "system-default": "Réamhshocrú an chórais", + "custom_fonts": "Úsáid clónna saincheaptha", + "preview": "Réamhamharc", + "monospace_font_description": "Úsáidte le haghaidh nótaí cóid agus bloic cóid", + "size_relative_to_general": "Tá an méid i gcoibhneas leis an méid cló ginearálta", + "apply_changes": "Athlódáil chun na hathruithe a chur i bhfeidhm" }, "max_content_width": { "title": "Leithead an Ábhair", @@ -1157,28 +1180,31 @@ "edited_notes_message": "Osclófar an cluaisín ribín Nótaí Eagarthóireachta go huathoibríoch ar nótaí lae" }, "theme": { - "title": "Téama an Iarratais", - "theme_label": "Téama", + "title": "Comhéadan Úsáideora", + "theme_label": "Téama an iarratais", "override_theme_fonts_label": "Sáraigh clónna téama", - "auto_theme": "Seanchóras (Lean scéim dathanna an chórais)", - "light_theme": "Oidhreacht (Éadrom)", - "dark_theme": "Oidhreacht (Dorcha)", - "triliumnext": "Trilium (Lean scéim dathanna an chórais)", - "triliumnext-light": "Trilium (Éadrom)", - "triliumnext-dark": "Trilium (Dorcha)", + "auto_theme": "Lean scéim dathanna an chórais", + "light_theme": "Solas", + "dark_theme": "Dorcha", + "triliumnext": "Lean scéim dathanna an chórais", + "triliumnext-light": "Solas", + "triliumnext-dark": "Dorcha", "layout": "Leagan Amach", "layout-vertical-title": "Ingearach", "layout-horizontal-title": "Cothrománach", "layout-vertical-description": "tá barra lainseála ar chlé (réamhshocraithe)", - "layout-horizontal-description": "Tá barra an lainseálaí faoin mbarra cluaisín, tá an barra cluaisín lánleithead anois." + "layout-horizontal-description": "Tá barra an lainseálaí faoin mbarra cluaisín, tá an barra cluaisín lánleithead anois.", + "modern_themes": "Nua-Aimseartha", + "legacy_themes": "Oidhreacht", + "custom_themes": "Saincheaptha" }, "ui-performance": { "title": "Feidhmíocht", - "enable-motion": "Cumasaigh aistrithe agus beochana", - "enable-shadows": "Cumasaigh scáthanna", - "enable-backdrop-effects": "Cumasaigh éifeachtaí cúlra do bhiachláir, fuinneoga aníos agus painéil", - "enable-smooth-scroll": "Cumasaigh scrollú réidh", - "app-restart-required": "(tá atosú an fheidhmchláir ag teastáil chun an t-athrú a chur i bhfeidhm)" + "enable-motion": "Aistrithe agus beochana", + "enable-shadows": "Scáthanna", + "enable-backdrop-effects": "Éifeachtaí cúlra do bhiachláir, fuinneoga aníos agus painéil", + "enable-smooth-scroll": "Scrolláil réidh", + "app-restart-required": "Éilíonn atosú an aip" }, "zoom_factor": { "title": "Fachtóir Súmáil (leagan deisce amháin)", @@ -1187,11 +1213,13 @@ "code_auto_read_only_size": { "title": "Méid Uathoibríoch Léite Amháin", "description": "Is é méid uathoibríoch nótaí inléite amháin an méid tar éis a dtaispeánfar nótaí i mód inléite amháin (ar chúiseanna feidhmíochta).", - "label": "Méid inléite amháin uathoibríoch (nótaí cóid)", + "label": "Méid inléite amháin uathoibríoch", "unit": "carachtair" }, "code-editor-options": { - "title": "Eagarthóir" + "title": "Eagarthóir", + "tab_width": "Leithead an chluaisín", + "tab_width_unit": "spásanna" }, "code_mime_types": { "title": "Cineálacha MIME atá ar fáil sa roghchlár anuas", @@ -1233,59 +1261,70 @@ "batch_ocr_error": "Earráid le linn próiseála baisce: {{error}}" }, "attachment_erasure_timeout": { - "attachment_erasure_timeout": "Am Teorann Scriosadh Ceangaltáin", - "erase_attachments_after": "Scrios ceangaltáin neamhúsáidte tar éis:", - "manual_erasing_description": "Is féidir leat scriosadh a spreagadh de láimh freisin (gan an t-am críochnaithe a shainmhínítear thuas a chur san áireamh):", - "erase_unused_attachments_now": "Scrios nótaí ceangaltáin neamhúsáidte anois", - "unused_attachments_erased": "Scriosadh ceangaltáin neamhúsáidte." + "attachment_erasure_timeout": "Ceangaltáin Neamhúsáidte", + "erase_attachments_after": "Scrios ceangaltáin neamhúsáidte ina dhiaidh sin", + "manual_erasing_description": "Spreag scriosadh de láimh, ag neamhaird den am scoir thuas.", + "erase_unused_attachments_now": "Scrios ceangaltáin neamhúsáidte anois", + "unused_attachments_erased": "Scriosadh ceangaltáin neamhúsáidte.", + "description": "Meastar nach n-úsáidtear ceangaltáin nach ndéantar tagairt dóibh a thuilleadh in aon nóta agus is féidir iad a scriosadh go huathoibríoch tar éis tréimhse ama.", + "erase_attachments_after_description": "Am sula scriostar ceangaltáin neamhúsáidte go buan." }, "network_connections": { - "network_connections_title": "Naisc Líonra", - "check_for_updates": "Seiceáil le haghaidh nuashonruithe go huathoibríoch" + "network_connections_title": "Líonra", + "check_for_updates": "Seiceáil le haghaidh nuashonruithe go huathoibríoch", + "check_for_updates_description": "Seiceálann sé le haghaidh leaganacha nua ar GitHub agus taispeánann sé fógra sa roghchlár domhanda nuair a bhíonn sé ar fáil." }, "note_erasure_timeout": { - "note_erasure_timeout_title": "Am Scriosadh Nótaí", - "erase_notes_after": "Scrios nótaí tar éis:", - "manual_erasing_description": "Is féidir leat scriosadh a spreagadh de láimh freisin (gan an t-am críochnaithe a shainmhínítear thuas a chur san áireamh):", + "note_erasure_timeout_title": "Nótaí Scriosta", + "erase_notes_after": "Scrios nótaí ina dhiaidh", + "manual_erasing_description": "Spreag scriosadh de láimh, ag neamhaird den am scoir thuas.", "erase_deleted_notes_now": "Scrios nótaí scriosta anois", - "deleted_notes_erased": "Tá nótaí scriosta scriosta." + "deleted_notes_erased": "Tá nótaí scriosta scriosta.", + "description": "Ní dhéantar nótaí scriosta a mharcáil ach mar scriosta ar dtús agus is féidir iad a aisghabháil ó Nótaí Le Déanaí. Tar éis tamaill, scriostar go buan iad.", + "erase_notes_after_description": "An t-am sula scriostar nótaí scriosta go buan." }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Eatramh Léirmheasa ar Nóta", "note_revisions_snapshot_description": "Is é an t-eatramh pictiúr athbhreithnithe nóta an t-am ina dhiaidh a chruthófar athbhreithniú nóta nua don nóta. Féach vicí le haghaidh tuilleadh eolais.", - "snapshot_time_interval_label": "Eatramh ama pictiúr athbhreithnithe nóta:" + "snapshot_time_interval_label": "Eatramh pictiúr", + "note_revisions_snapshot_description_short": "An t-am a gcruthófar athbhreithniú nóta nua ina dhiaidh." }, "revisions_snapshot_limit": { "note_revisions_snapshot_limit_title": "Teorainn ar Ghrianghraf Athbhreithnithe Nóta", "note_revisions_snapshot_limit_description": "Tagraíonn an teorainn ar líon na n-athbhreithnithe nótaí don líon uasta athbhreithnithe is féidir a shábháil do gach nóta. Ciallaíonn -1 gan aon teorainn, ciallaíonn 0 scriosadh na hathbhreithnithe go léir. Is féidir leat an líon uasta athbhreithnithe a shocrú do nóta aonair tríd an lipéad #versioningLimit.", - "snapshot_number_limit_label": "Teorainn líon na n-íomhánna athbhreithnithe nóta:", + "snapshot_number_limit_label": "Uasmhéid athbhreithnithe", "snapshot_number_limit_unit": "léargais", "erase_excess_revision_snapshots": "Scrios na léargais athbhreithnithe breise anois", - "erase_excess_revision_snapshots_prompt": "Scriosadh na léargais bhreise athbhreithnithe." + "erase_excess_revision_snapshots_prompt": "Scriosadh na léargais bhreise athbhreithnithe.", + "note_revisions_snapshot_limit_description_short": "Uasmhéid athbhreithnithe in aghaidh an nóta. Úsáid -1 le haghaidh neamhtheoranta, 0 le díchumasú.", + "erase_excess_revision_snapshots_description": "Scrios athbhreithnithe a sháraíonn an teorainn do na nótaí uile." }, "search_engine": { "title": "Inneall Cuardaigh", - "custom_search_engine_info": "Éilíonn inneall cuardaigh saincheaptha go socrófar ainm agus URL araon. Mura socraítear ceachtar acu seo, úsáidfear DuckDuckGo mar an t-inneall cuardaigh réamhshocraithe.", - "predefined_templates_label": "Teimpléid inneall cuardaigh réamhshainithe", + "custom_search_engine_info": "Úsáidtear é seo agus téacs roghnaithe á chuardach ar an ngréasán. Mura bhfuil sé cumraithe, úsáidfear DuckDuckGo.", + "predefined_templates_label": "Réamhshocruithe", "bing": "Bing", "baidu": "Baidu", "duckduckgo": "DuckDuckGo", "google": "Google", - "custom_name_label": "Ainm innill chuardaigh saincheaptha", - "custom_name_placeholder": "Saincheap ainm an innill chuardaigh", - "custom_url_label": "Ba chóir go mbeadh {keyword} san áireamh mar áitchoinneálaí don téarma cuardaigh i URL inneall cuardaigh saincheaptha.", - "custom_url_placeholder": "Saincheap URL an innill chuardaigh", - "save_button": "Sábháil" + "custom_name_label": "Ainm", + "custom_name_placeholder": "Ainm an innill chuardaigh", + "custom_url_label": "URL", + "custom_url_placeholder": "URL inneall cuardaigh", + "save_button": "Sábháil", + "custom_url_description": "Úsáid {keyword} mar áitchoinneálaí don téarma cuardaigh." }, "tray": { "title": "Tráidire an Chórais", - "enable_tray": "Cumasaigh an tráidire (ní mór Trilium a atosú le go dtiocfaidh an t-athrú seo i bhfeidhm)" + "enable_tray": "Deilbhín tráidire", + "enable_tray_description": "Caithfear Trilium a atosú chun go dtiocfaidh an t-athrú seo i bhfeidhm." }, "heading_style": { - "title": "Stíl Ceannteidil", + "title": "Stíl ceannteidil", "plain": "Simplí", "underline": "Folínigh", - "markdown": "Stíl marcála síos" + "markdown": "Stíl marcála síos", + "description": "Stíl amhairc do cheannteidil i nótaí téacs." }, "highlights_list": { "title": "Liosta Buaicphointí", @@ -1309,14 +1348,16 @@ "text_auto_read_only_size": { "title": "Méid Uathoibríoch Léite Amháin", "description": "Is é méid uathoibríoch nótaí inléite amháin an méid tar éis a dtaispeánfar nótaí i mód inléite amháin (ar chúiseanna feidhmíochta).", - "label": "Méid inléite amháin uathoibríoch (nótaí téacs)", + "label": "Méid inléite amháin uathoibríoch", "unit": "carachtair" }, "custom_date_time_format": { - "title": "Formáid Dáta/Am Saincheaptha", + "title": "Formáid dáta/ama", "description": "Saincheap formáid an dáta agus an ama a chuirtear isteach trí nó an barra uirlisí. Féach ar doiciméid Day.js le haghaidh na gcomharthaí formáide atá ar fáil.", - "format_string": "Formáid teaghrán:", - "formatted_time": "Dáta/am formáidithe:" + "format_string": "Formáid teaghrán", + "formatted_time": "Dáta/am formáidithe", + "description_short": "Saincheap formáid an dáta agus an ama a chuirtear isteach tríd an mbarra uirlisí.", + "preview": "Réamhamharc: {{preview}}" }, "i18n": { "title": "Logánú", @@ -1331,19 +1372,19 @@ "sunday": "Dé Domhnaigh", "first-week-of-the-year": "An chéad seachtain den bhliain", "first-week-contains-first-day": "Tá an chéad lá den bhliain sa chéad seachtain", - "first-week-contains-first-thursday": "Tá an chéad Déardaoin den bhliain sa chéad seachtain", + "first-week-contains-first-thursday": "Tá an chéad Déardaoin sa chéad seachtain (ISO 8601)", "first-week-has-minimum-days": "Tá an chéad seachtain ag an íosmhéid laethanta", "min-days-in-first-week": "Íosmhéid laethanta sa chéad seachtain", - "first-week-warning": "D’fhéadfadh sé go mbeadh dúblach le Nótaí Seachtaine atá ann cheana féin mar thoradh ar athrú roghanna na chéad seachtaine agus ní dhéanfar na Nótaí Seachtaine atá ann cheana a nuashonrú dá réir.", + "first-week-warning": "D’fhéadfadh sé go mbeadh dúblaigh le Nótaí Seachtaine atá ann cheana féin mar thoradh ar athrú seo.", "formatting-locale": "Formáid dáta agus uimhreach", "formatting-locale-auto": "Bunaithe ar theanga an fheidhmchláir" }, "backup": { "automatic_backup": "Cúltaca uathoibríoch", "automatic_backup_description": "Is féidir le Trilium cúltaca den bhunachar sonraí a dhéanamh go huathoibríoch:", - "enable_daily_backup": "Cumasaigh cúltaca laethúil", - "enable_weekly_backup": "Cumasaigh cúltaca seachtainiúil", - "enable_monthly_backup": "Cumasaigh cúltaca míosúil", + "enable_daily_backup": "Cúltaca laethúil", + "enable_weekly_backup": "Cúltaca seachtainiúil", + "enable_monthly_backup": "Cúltaca míosúil", "backup_recommendation": "Moltar an cúltaca a choinneáil casta air, ach is féidir leis seo moill a chur ar thosú feidhmchlár le bunachair shonraí móra agus/nó gléasanna stórála mall.", "backup_now": "Cúltaca anois", "backup_database_now": "Cúltaca bunachar sonraí anois", @@ -1352,7 +1393,8 @@ "path": "Cosán", "database_backed_up_to": "Tá cúltaca déanta den bhunachar sonraí chuig {{backupFilePath}}", "no_backup_yet": "gan aon chúltaca fós", - "download": "Íoslódáil" + "download": "Íoslódáil", + "title": "Cúltaca" }, "etapi": { "title": "ETAPI", @@ -1387,18 +1429,22 @@ "new_password": "Pasfhocal nua", "new_password_confirmation": "Deimhniú pasfhocail nua", "change_password": "Athraigh an focal faire", - "protected_session_timeout": "Am Teorann Seisiúin Chosanta", - "protected_session_timeout_description": "Is tréimhse ama í an t-am scoir seisiúin chosanta a scriostar an seisiún cosanta ó chuimhne an bhrabhsálaí ina dhiaidh. Déantar é seo a thomhas ón idirghníomhaíocht dheireanach le nótaí cosanta. Féach", + "protected_session_timeout": "Seisiún faoi Chosaint", + "protected_session_timeout_description": "Am neamhghníomhaíochta sula nglanfar an seisiún ó chuimhne an bhrabhsálaí. Féach", "wiki": "vicí", "for_more_info": "le haghaidh tuilleadh eolais.", - "protected_session_timeout_label": "Am scoir seisiúin faoi chosaint:", + "protected_session_timeout_label": "Dún seisiún go huathoibríoch ina dhiaidh sin", "reset_confirmation": "Trí an focal faire a athshocrú caillfidh tú rochtain go deo ar do nótaí cosanta go léir atá ann cheana féin. An bhfuil tú cinnte gur mhaith leat an focal faire a athshocrú?", "reset_success_message": "Athshocraíodh an focal faire. Socraigh focal faire nua le do thoil", "change_password_heading": "Athraigh Pasfhocal", "set_password_heading": "Socraigh Pasfhocal", "set_password": "Socraigh Pasfhocal", "password_mismatch": "Ní hionann pasfhocail nua.", - "password_changed_success": "Athraíodh an focal faire. Athlódálfar Trilium tar éis duit brúigh OK." + "password_changed_success": "Athraíodh an focal faire. Athlódálfar Trilium tar éis duit brúigh OK.", + "change_password_description": "Nuashonraigh do phasfhocal reatha", + "reset_password": "Athshocraigh an focal faire", + "reset_password_description": "Caill rochtain ar nótaí faoi chosaint go buan", + "cancel": "Cealaigh" }, "multi_factor_authentication": { "title": "Fíordheimhniú Ilfhachtóireach", @@ -1461,17 +1507,19 @@ "related_description": "Cumraigh teangacha seiceála litrithe agus foclóir saincheaptha." }, "sync_2": { - "config_title": "Cumraíocht Sioncrónaithe", - "server_address": "Seoladh sampla an fhreastalaí", - "timeout": "Am scoir sioncrónaithe", - "proxy_label": "Sioncrónaigh freastalaí seachfhreastalaí (roghnach)", + "config_title": "Freastalaí Sioncrónaithe", + "server_address": "Seoladh an fhreastalaí", + "timeout": "Am críochnaithe nasc", + "proxy_label": "Freastalaí seachfhreastalaí", "save": "Sábháil", "help": "Cabhair", - "test_title": "Tástáil Sioncrónaithe", - "test_description": "Déanfaidh sé seo tástáil ar an nasc agus ar an gcroitheadh láimhe leis an bhfreastalaí sioncrónaithe. Mura bhfuil an freastalaí sioncrónaithe tosaithe, socróidh sé seo é chun sioncrónú leis an doiciméad áitiúil.", + "test_title": "Tástáil Ceangal", + "test_description": "Déan tástáil ar an nasc leis an bhfreastalaí sioncrónaithe. Mura ndéantar é a thosú, socróidh sé seo sioncrónú.", "test_button": "Tástáil sioncrónaithe", "handshake_failed": "Theip ar chroitheadh láimhe an fhreastalaí sioncrónaithe, earráid: {{message}}", - "timeout_description": "Cé chomh fada is ceart fanacht sula dtugann tú suas ar nasc sioncrónaithe mall. Méadaigh an méid ama má tá líonra éagobhsaí agat." + "timeout_description": "Am fanacht sula dtugann tú suas ar nasc mall.", + "server_address_description": "URL an fhreastalaí Trilium le sioncrónú leis.", + "proxy_description": "Fág bán chun seachfhreastalaí córais a úsáid (deasc amháin). Bain úsáid as \"noproxy\" chun na seachfhreastalaithe go léir a sheachaint." }, "api_log": { "close": "Dún" @@ -1657,7 +1705,7 @@ "note_detail": { "could_not_find_typewidget": "Níorbh fhéidir typeWidget a aimsiú don chineál '{{type}}'", "printing": "Priontáil ar siúl...", - "printing_pdf": "Ag easpórtáil go PDF ar siúl...", + "printing_pdf": "Réamhamharc priontála á ullmhú...", "print_report_title": "Tuarascáil a phriontáil", "print_report_collection_content_one": "Níorbh fhéidir nóta {{count}} sa bhailiúchán a phriontáil mar nach dtacaítear leo nó mar go bhfuil siad faoi chosaint.", "print_report_collection_content_two": "Níorbh fhéidir {{count}} nótaí sa bhailiúchán a phriontáil mar nach dtacaítear leo nó mar go bhfuil siad faoi chosaint.", @@ -1786,7 +1834,9 @@ "theme_group_light": "Téamaí éadroma", "theme_group_dark": "Téamaí dorcha", "copy_title": "Cóipeáil chuig an ghearrthaisce", - "click_to_copy": "Cliceáil chun cóipeáil" + "click_to_copy": "Cliceáil chun cóipeáil", + "tab_width": "Leithead an chluaisín", + "tab_width_unit": "spásanna" }, "classic_editor_toolbar": { "title": "Formáidiú" @@ -1805,7 +1855,8 @@ "title": "Seasta", "description": "Feictear uirlisí eagarthóireachta sa chluaisín ribín \"Formáidiú\"." }, - "multiline-toolbar": "Taispeáin an barra uirlisí ar illínte mura n-oireann sé." + "multiline-toolbar": "Taispeáin an barra uirlisí ar illínte mura n-oireann sé", + "toolbar_style": "Stíl an bharra uirlisí" } }, "electron_context_menu": { @@ -1872,7 +1923,7 @@ "days": "Laethanta" }, "share": { - "title": "Socruithe Comhroinnte", + "title": "Comhroinn", "redirect_bare_domain": "Atreoraigh fearann lom chuig an leathanach Comhroinnte", "redirect_bare_domain_description": "Atreoraigh úsáideoirí gan ainm chuig an leathanach Comhroinnte in ionad Logáil Isteach a thaispeáint", "show_login_link": "Taispeáin nasc Logála Isteach sa téama Comhroinnte", @@ -1936,12 +1987,12 @@ }, "editorfeatures": { "title": "Gnéithe", - "emoji_completion_enabled": "Cumasaigh uath-chomhlánú Emoji", - "emoji_completion_description": "Más cumasaithe é, is féidir emojis a chur isteach i dtéacs go héasca trí `:` a chlóscríobh, agus ainm emoji ina dhiaidh sin.", - "note_completion_enabled": "Cumasaigh uath-chríochnú nótaí", - "note_completion_description": "Más cumasaithe é, is féidir naisc chuig nótaí a chruthú trí `@` a chlóscríobh agus teideal an nóta ina dhiaidh sin.", - "slash_commands_enabled": "Cumasaigh orduithe slaise", - "slash_commands_description": "Más cumasaithe é, is féidir orduithe eagarthóireachta amhail briseadh líne nó ceannteidil a chur isteach a athrú trí `/` a chlóscríobh." + "emoji_completion_enabled": "Uath-chomhlánú emoji", + "emoji_completion_description": "Uath-líonadh emoji Is féidir emojis a chur isteach i dtéacs go héasca trí `:` a chlóscríobh, agus ainm emoji ina dhiaidh sin.", + "note_completion_enabled": "Uath-chomhlánú nótaí", + "note_completion_description": "Is féidir naisc chuig nótaí a chruthú trí `@` a chlóscríobh agus teideal an nóta ina dhiaidh sin.", + "slash_commands_enabled": "Orduithe slaise", + "slash_commands_description": "Is féidir orduithe eagarthóireachta amhail briseadh líne nó ceannteidil a chur isteach a athrú trí `/` a chlóscríobh." }, "table_view": { "new-row": "Sraith nua", @@ -2045,7 +2096,9 @@ "related_code_notes": "Scéim dathanna le haghaidh nótaí cóid", "ui": "Comhéadan úsáideora", "ui_old_layout": "Leagan amach sean", - "ui_new_layout": "Leagan amach nua" + "ui_new_layout": "Leagan amach nua", + "ui_layout_style": "Stíl leagan amach", + "ui_layout_orientation": "Treoshuíomh an bharra lainseálaí" }, "units": { "percentage": "%" @@ -2144,7 +2197,19 @@ "note_paths_many": "{{count}} cosáin", "note_paths_other": "{{count}} cosáin", "note_paths_title": "Cosáin nótaí", - "code_note_switcher": "Athraigh mód teanga" + "code_note_switcher": "Athraigh mód teanga", + "tab_width": "Leithead an Chluaisín: {{width}}", + "tab_width_title": "Athraigh leithead an chluaisín", + "tab_width_spaces": "{{count}} spásanna", + "tab_width_spaces_short": "Spásanna: {{width}}", + "tab_width_tabs": "Cluaisíní: {{width}}", + "tab_width_use_default": "Úsáid an réamhshocrú ({{width}})", + "tab_width_use_default_style": "Úsáid réamhshocraithe ({{style}})", + "tab_width_display_header": "Leithead taispeána", + "tab_width_reindent_header": "Ath-eangaigh ábhar go", + "tab_width_style_header": "Líon isteach ag baint úsáide as", + "tab_width_style_spaces": "Spásanna", + "tab_width_style_tabs": "Cluaisíní" }, "attributes_panel": { "title": "Tréithe Nóta" @@ -2398,5 +2463,55 @@ "processing_complete": "Próiseáil OCR críochnaithe.", "text_filtered_low_confidence": "Bhraith OCR téacs le muinín {{confidence}}%, ach caitheadh leis é mar is é {{threshold}}% an tairseach íosta atá agat.", "open_media_settings": "Oscail Socruithe" + }, + "revisions": { + "title": "Athbhreithnithe Nótaí" + }, + "database": { + "title": "Bunachar Sonraí" + }, + "search": { + "title": "Cuardaigh", + "fuzzy_matching_label": "Caoinfhulaingt clóscríofa sa chuardach", + "fuzzy_matching_description": "Bíonn tionchar aige ar chuardach tapa agus ar chuardach iomlán. Faightear focail chomhchosúla nuair nach leor na meaitseáil chruinne.", + "autocomplete_fuzzy_label": "Caoinfhulaingt clóscríofa in uathchríochnú", + "autocomplete_fuzzy_description": "Bíonn tionchar aige ar léim-go-nóta agus ar roghnóirí nótaí. Níos moille ach glactar le clóscríobh." + }, + "text_editor": { + "title": "Eagarthóir" + }, + "link": { + "failed_to_open": "Theip ar oscailt an nasc '{{- href}}': {{- message}}" + }, + "print_preview": { + "title": "Réamhamharc priontála", + "close": "Dún", + "save": "Sábháil mar PDF", + "print": "Priontáil", + "export_pdf": "Easpórtáil mar PDF", + "system_print": "Priontáil ag baint úsáide as dialóg an chórais", + "destination": "Ceann Scríbe", + "destination_pdf": "Sábháil mar PDF", + "destination_printers": "Printéirí", + "destination_default": "Réamhshocrú", + "orientation": "Treoshuíomh", + "portrait": "Portráid", + "landscape": "Tírdhreach", + "page_size": "Méid leathanaigh", + "scale": "Scála", + "margins": "Imill", + "render_error": "Ní féidir PDF a rindreáil leis na socruithe reatha. Seiceáil na corrlaigh agus an scála le do thoil.", + "margins_default": "Réamhshocrú", + "margins_none": "Dada", + "margins_minimum": "Íosmhéid", + "margins_custom": "Saincheaptha", + "margin_top": "Barr", + "margin_right": "Ar dheis", + "margin_bottom": "Bun", + "margin_left": "Ar chlé", + "page_ranges": "Leathanaigh", + "page_ranges_hint": "Fág folamh chun na leathanaigh go léir a phriontáil.", + "page_ranges_invalid": "Formáid neamhbhailí. Úsáid m.sh. 1-5, 8, 11-13.", + "page_ranges_placeholder": "m.sh. 1-5, 8, 11-13" } } From 09d1c85bd36395479ac5fab67907ea064679f784 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 16 Apr 2026 17:06:42 +0200 Subject: [PATCH 199/203] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: Trilium Notes/README Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ --- docs/README-ug.md | 99 +++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/docs/README-ug.md b/docs/README-ug.md index 0c4c59b054..b20038be1e 100644 --- a/docs/README-ug.md +++ b/docs/README-ug.md @@ -176,77 +176,81 @@ zadam/Trilium نۇسخىسىدىن TriliumNext/Notes غا كۆچۈش ئۈچۈن ئۇلانغان - [GitHub Discussions](https://github.com/TriliumNext/Trilium/discussions) (ئاسىنكرون مۇنازىرە). -- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug - reports and feature requests.) +- [GitHub Issues](https://github.com/TriliumNext/Trilium/issues) (خاتالىق مەلۇم + قىلىش ۋە ئىقتىدار ئېھتىياجىنى ئوتتۇرىغا قويۇش). -## 🏗 Installation +## 🏗 قاچىلاش ### Windows / MacOS -Download the binary release for your platform from the [latest release -page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package -and run the `trilium` executable. +[ئەڭ يېڭى نەشرى بېتى](https://github.com/TriliumNext/Trilium/releases/latest)دىن +سۇپىڭىزغا ماس كېلىدىغان سىستېما ھۆججىتىنى چۈشۈرۈڭ، پرېستىن يەشكەندىن كېيىن +`trilium` ئىجرا ھۆججىتىنى قوزغىتىڭ.) ### Linux -If your distribution is listed in the table below, use your distribution's -package. +ئەگەر سىز ئىشلىتىۋاتقان نەشرى تۆۋەندىكى جەدۋەلدە بولسا، شۇ نەشرىنىڭ يۇمشاق دېتال +بوغچىسىنى ئىشلىتىڭ. [![Packaging status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions) -You may also download the binary release for your platform from the [latest -release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the -package and run the `trilium` executable. +سىز يەنە [ئەڭ يېڭى ئېلان قىلىنغان +بەت](https://github.com/TriliumNext/Trilium/releases/latest)تىن مۇناسىپ سۇپىنىڭ +سىستېما ھۆججىتىنى چۈشۈرۈپ، پرېستىن يېشىپ بولغاندىن كېيىن trilium ئىجرا ھۆججىتىنى +قوزغاتسىڭىز بولىدۇ. -TriliumNext is also provided as a Flatpak, but not yet published on FlatHub. +TriliumNext يەنە Flatpak نۇسخىسى بىلەن تەمىنلەيدۇ، ئەمما تېخى FlatHub غا +چىقىرىلمىدى. ### Browser (any OS) -If you use a server installation (see below), you can directly access the web -interface (which is almost identical to the desktop app). +ئەگەر سىزدە (تۆۋەندىكىدەك) مۇلازىمىتىر قاچىلانمىسى بولسا، تور كۆرۈنمە يۈزىنى +بىۋاسىتە ساقىلالايسىز (ئۇ بىر تەرەپ قىلغۇچ دېتالى بىلەن ئاساسەن ئوخشاش) -Currently only the latest versions of Chrome & Firefox are supported (and -tested). +نۆۋەتتە پەقەت Chrome ۋە Firefox نىڭ ئەڭ يېڭى نەشرىنىلا قوللايدۇ (ھەمدە ئەمەلىي +سىناقتىن ئۆتكەن). ### Mobile -To use TriliumNext on a mobile device, you can use a mobile web browser to -access the mobile interface of a server installation (see below). +ئەگەر كۆچمە ئۈسكۈنىلەردە TriliumNext نى ئىشلەتمەكچى بولسىڭىز، كۆچمە كۆرگۈچ +ئارقىلىق مۇلازىمىتىرغا قاچىلانغان كۆچمە نۇسخا كۆرۈنمە يۈزىنى زىيارەت قىلالايسىز +(تۆۋەنگە قاراڭ). -See issue https://github.com/TriliumNext/Trilium/issues/4962 for more -information on mobile app support. +كۆچمە دېتال قوللاشقا ئائىت تېخىمۇ كۆپ ئۇچۇرلار ئۈچۈن، +https://github.com/TriliumNext/Trilium/issues/4962 مەسىلە بېتىگە قاراڭ. -If you prefer a native Android app, you can use -[TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid). -Report bugs and missing features at [their -repository](https://github.com/FliegendeWurst/TriliumDroid). Note: It is best to -disable automatic updates on your server installation (see below) when using -TriliumDroid since the sync version must match between Trilium and TriliumDroid. +ئەگەر سىز ئەسلى Android دېتالىنى ئىشلىتىشكە ئامراق بولسىڭىز، +[TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid) +نى ئىشلەتسىڭىز بولىدۇ. مەسىلە ياكى كەم بولغان ئىقتىدارلارنى مەلۇم قىلماقچى +بولسىڭىز، [ئۇنىڭ ئامبىرى](https://github.com/FliegendeWurst/TriliumDroid) غا +مەرھەمەت. ### Server -To install TriliumNext on your own server (including via Docker from -[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server -installation docs](https://docs.triliumnotes.org/user-guide/setup/server). +ئەگەر ئۆزىڭىزنىڭ مۇلازىمىتىرىغا TriliumNext نى قاچىلىماقچى بولسىڭىز ([Docker +Hub](https://hub.docker.com/r/triliumnext/trilium) دىن Docker ئارقىلىق +ئورۇنلاشتۇرۇشنىمۇ ئۆز ئىچىگە ئالىدۇ)، [مۇلازىمىتىر قاچىلاش +ھۆججىتى](https://docs.triliumnotes.org/user-guide/setup/server) گە ئەمەل قىلىڭ. -## 💻 Contribute +## 💻 تۆھپە قوشۇش -### Translations +### تەرجىمە -If you are a native speaker, help us translate Trilium by heading over to our -[Weblate page](https://hosted.weblate.org/engage/trilium/). +ئەگەر سىز شۇ تىلنىڭ ئىگىسى بولسىڭىز، بىزنىڭ [Weblate +بېتىمىز](https://hosted.weblate.org/engage/trilium/)گە كىرىپ Trilium نىڭ تەرجىمە +قىلىنىشىغا ياردەم بېرىشىڭىزنى قارشى ئالىمىز. -Here's the language coverage we have so far: +تۆۋەندىكىسى نۆۋەتتىكى تىللارنىڭ قاپلىنىش ئەھۋالى: -[![Translation -status](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/) +[![تەرجىمە +ئەھۋالى](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/) -### Code +### پروگرامما كودى -Download the repository, install dependencies using `pnpm` and then run the -server (available at http://localhost:8080): +ئامبارنى چۈشۈرۈڭ، `pnpm` ئارقىلىق بېقىندى زاپچاسلارنى قاچىلاڭ، ئاندىن +مۇلازىمىتىرنى قوزغىتىڭ (مۇلازىمەت ئادرېسى: http://localhost:8080): ```shell git clone https://github.com/TriliumNext/Trilium.git cd Trilium @@ -254,10 +258,10 @@ pnpm install pnpm run server:start ``` -### Documentation +### ھۆججەت -Download the repository, install dependencies using `pnpm` and then run the -environment required to edit the documentation: +ئامبارنى چۈشۈرۈڭ، `pnpm` ئارقىلىق بېقىندى زاپچاسلارنى قاچىلاڭ، ئاندىن ھۆججەت +تەھرىرلەشكە ئېھتىياجلىق مۇھىتنى قوزغىتىڭ: ```shell git clone https://github.com/TriliumNext/Trilium.git cd Trilium @@ -266,8 +270,8 @@ pnpm edit-docs:edit-docs ``` ### Building the Executable -Download the repository, install dependencies using `pnpm` and then build the -desktop app for Windows: +ئامبارنى چۈشۈرۈڭ، `pnpm` ئارقىلىق بېقىندى زاپچاسلارنى قاچىلاڭ، ئاندىن Windows +ئۈچۈن ئۈستەلئۈستى ئەپىنى قۇرۇپ چىقىڭ: ```shell git clone https://github.com/TriliumNext/Trilium.git cd Trilium @@ -275,10 +279,11 @@ pnpm install pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32 ``` -For more details, see the [development -docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide). +تېخىمۇ كۆپ تەپسىلاتلار ئۈچۈن [تەتقىقات +ھۆججەت](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide)گە +قاراڭ. -### Developer Documentation +### تەتقىقاتچى ھۆججەتلىرى Please view the [documentation guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) From d8db862c22167f5513e2cf2af62fb70d46541124 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 16 Apr 2026 18:25:37 +0300 Subject: [PATCH 200/203] test(client,server): guard against duplicate JSON keys --- apps/client/src/services/i18n.spec.ts | 11 +++- apps/server/src/services/i18n.spec.ts | 11 +++- packages/commons/src/lib/test-utils.spec.ts | 25 ++++++++- packages/commons/src/lib/test-utils.ts | 61 +++++++++++++++++++++ 4 files changed, 103 insertions(+), 5 deletions(-) diff --git a/apps/client/src/services/i18n.spec.ts b/apps/client/src/services/i18n.spec.ts index e408816436..3216c6cf0c 100644 --- a/apps/client/src/services/i18n.spec.ts +++ b/apps/client/src/services/i18n.spec.ts @@ -1,10 +1,10 @@ -import { LOCALES } from "@triliumnext/commons"; +import { findDuplicateJsonKeys, LOCALES } from "@triliumnext/commons"; import { readFileSync } from "fs"; import { join } from "path"; import { describe, expect, it } from "vitest"; describe("i18n", () => { - it("translations are valid JSON", () => { + it("translations are valid JSON with no duplicate keys", () => { for (const locale of LOCALES) { if (locale.contentOnly || locale.id === "en_rtl") { continue; @@ -14,6 +14,13 @@ describe("i18n", () => { const translationFile = readFileSync(translationPath, { encoding: "utf-8" }); expect(() => JSON.parse(translationFile), `JSON error while parsing locale '${locale.id}' at "${translationPath}"`) .not.toThrow(); + + const duplicates = findDuplicateJsonKeys(translationFile); + expect( + duplicates, + `Duplicate keys in locale '${locale.id}' at "${translationPath}":\n${ + duplicates.map((d) => ` - "${d.key}" (line ${d.line})`).join("\n")}` + ).toEqual([]); } }); }); diff --git a/apps/server/src/services/i18n.spec.ts b/apps/server/src/services/i18n.spec.ts index e9be7efc81..fba64cdb7b 100644 --- a/apps/server/src/services/i18n.spec.ts +++ b/apps/server/src/services/i18n.spec.ts @@ -1,4 +1,4 @@ -import { LOCALES } from "@triliumnext/commons"; +import { findDuplicateJsonKeys, LOCALES } from "@triliumnext/commons"; import { readFileSync } from "fs"; import { join } from "path"; // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -6,7 +6,7 @@ const { languages } = require("tesseract.js"); import { describe, expect, it } from "vitest"; describe("i18n", () => { - it("translations are valid JSON", () => { + it("translations are valid JSON with no duplicate keys", () => { for (const locale of LOCALES) { if (locale.contentOnly || locale.id === "en_rtl") { continue; @@ -16,6 +16,13 @@ describe("i18n", () => { const translationFile = readFileSync(translationPath, { encoding: "utf-8" }); expect(() => JSON.parse(translationFile), `JSON error while parsing locale '${locale.id}' at "${translationPath}"`) .not.toThrow(); + + const duplicates = findDuplicateJsonKeys(translationFile); + expect( + duplicates, + `Duplicate keys in locale '${locale.id}' at "${translationPath}":\n` + + duplicates.map((d) => ` - "${d.key}" (line ${d.line})`).join("\n") + ).toEqual([]); } }); diff --git a/packages/commons/src/lib/test-utils.spec.ts b/packages/commons/src/lib/test-utils.spec.ts index 4d02fc90d1..90388f5814 100644 --- a/packages/commons/src/lib/test-utils.spec.ts +++ b/packages/commons/src/lib/test-utils.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { trimIndentation } from "./test-utils.js"; +import { findDuplicateJsonKeys, trimIndentation } from "./test-utils.js"; describe("Utils", () => { it("trims indentation", () => { @@ -11,4 +11,27 @@ Hello world 123`); }); + + describe("findDuplicateJsonKeys", () => { + it("returns empty for valid JSON without duplicates", () => { + expect(findDuplicateJsonKeys(`{"a": 1, "b": {"c": 2}}`)).toEqual([]); + }); + + it("detects duplicates at the top level and reports line numbers", () => { + const text = `{\n "a": 1,\n "b": 2,\n "a": 3\n}`; + expect(findDuplicateJsonKeys(text)).toEqual([{ key: "a", line: 4 }]); + }); + + it("scopes keys per object — same name at different levels is not a duplicate", () => { + expect(findDuplicateJsonKeys(`{"a": {"x": 1}, "b": {"x": 2}}`)).toEqual([]); + }); + + it("does not treat string values containing a colon as keys", () => { + expect(findDuplicateJsonKeys(`{"a": "b:c", "d": "a:e"}`)).toEqual([]); + }); + + it("does not treat strings inside arrays as keys", () => { + expect(findDuplicateJsonKeys(`{"items": ["a", "a", "b"]}`)).toEqual([]); + }); + }); }); diff --git a/packages/commons/src/lib/test-utils.ts b/packages/commons/src/lib/test-utils.ts index 86ebfb0d60..a570d00440 100644 --- a/packages/commons/src/lib/test-utils.ts +++ b/packages/commons/src/lib/test-utils.ts @@ -62,3 +62,64 @@ export function flushPromises() { export function sleepFor(duration: number) { return new Promise(resolve => setTimeout(resolve, duration)); } + +/** + * Scans raw JSON text for keys that are duplicated within the same object. + * + * `JSON.parse` silently collapses duplicate keys (the last one wins), which makes + * it impossible to detect them from the parsed value. This scanner walks the raw + * text, pushing/popping a scope for each `{`/`}`, and identifies a string as a + * key when the next non-whitespace char is `:`. + * + * Intended for validating hand-maintained JSON files (e.g. translation files) + * at test level. + */ +export function findDuplicateJsonKeys(text: string): Array<{ key: string; line: number }> { + const duplicates: Array<{ key: string; line: number }> = []; + const stack: Set[] = []; + let line = 1; + let i = 0; + + while (i < text.length) { + const c = text[i]; + if (c === "\n") { + line++; + i++; + } else if (c === "{") { + stack.push(new Set()); + i++; + } else if (c === "}") { + stack.pop(); + i++; + } else if (c === '"') { + const start = i; + const startLine = line; + i++; + while (i < text.length && text[i] !== '"') { + if (text[i] === "\\") { + i += 2; + } else { + if (text[i] === "\n") line++; + i++; + } + } + i++; + // A string is a key iff the next non-whitespace char is ':'. + let j = i; + while (j < text.length && /\s/.test(text[j])) j++; + if (text[j] === ":" && stack.length > 0) { + const key = JSON.parse(text.substring(start, i)) as string; + const frame = stack[stack.length - 1]; + if (frame.has(key)) { + duplicates.push({ key, line: startLine }); + } else { + frame.add(key); + } + } + } else { + i++; + } + } + + return duplicates; +} From 603b232d1f0928c2cf35dd1746695c16f83edd9b Mon Sep 17 00:00:00 2001 From: passkal4 Date: Thu, 16 Apr 2026 15:10:23 +0200 Subject: [PATCH 201/203] Translated using Weblate (Uyghur) Currently translated at 21.3% (86 of 403 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ug/ --- .../src/assets/translations/ug/server.json | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/ug/server.json b/apps/server/src/assets/translations/ug/server.json index 593275bcb8..7f7380ddaa 100644 --- a/apps/server/src/assets/translations/ug/server.json +++ b/apps/server/src/assets/translations/ug/server.json @@ -57,6 +57,34 @@ "show-recent-changes": "يېقىنقى ئۆزگىرىشلەر سۆھبەت رامكىسىنى كۆرسىتىش", "show-sql-console": "SQL كونترول سۇپىسى سۆھبەت رامكىسىنى كۆرسىتىش", "show-backend-log": "ئارقا سۇپا خاتىرىسى سۆھبەت رامكىسىنى كۆرسىتىش", - "show-help": "ياردەمنى كۆرسىتىش" + "show-help": "ياردەمنى كۆرسىتىش", + "show-cheatsheet": "تېز كۇنۇپكا يېتەكچىسىنى كۆرسىتىش", + "text-note-operations": "تېكىستلىك خاتىرە مەشغۇلاتى", + "add-link-to-text": "تېكىستكە ئۇلانما قوشۇش سۆھبەت رامكىسىنى ئېچىش", + "follow-link-under-cursor": "كۇرسور ئاستىدىكى ئۇلانمىنى ئېچىش", + "edit-readonly-note": "پەقەت ئوقۇشقىلا بولىدىغان خاتىرىنى تەھرىرلەش", + "attributes-labels-and-relations": "خاسلىق (بەلگە ۋە مۇناسىۋەت)", + "add-new-label": "يېڭى بەلگە قۇرۇش", + "insert-date-and-time-to-text": "تېكىستكە چېسلا ۋە ۋاقىت قىستۇرۇش", + "paste-markdown-into-text": "چاپلاش تاختىسىدىكى Markdown نى تېكىستلىك خاتىرىگە چاپلاش", + "cut-into-note": "نۆۋەتتىكى خاتىرىنىڭ تاللانغان قىسمىنى كېسىۋېلىش ھەمدە تاللانغان تېكىستنى ئۆز ئىچىگە ئالغان تارماق خاتىرە قۇرۇش", + "create-new-relation": "يېڭى مۇناسىۋەت قۇرۇش", + "ribbon-tabs": "ئىقتىدار رايونى بەتلىرى", + "toggle-link-map": "ئۇلىنىش خەرىتىسىنى ئالماشتۇرۇش", + "toggle-note-info": "خاتىرە ئۇچۇرلىرىنى ئالماشتۇرۇش", + "toggle-note-paths": "خاتىرە يولىنى ئالماشتۇرۇش", + "toggle-similar-notes": "ئوخشاش خاتىرىلەرنى ئالماشتۇرۇش", + "other": "باشقىلار", + "toggle-right-pane": "مۇندەرىجە ۋە يارقىنلىتىشنى ئۆز ئىچىگە ئالغان ئوڭ تەرەپ كۆزنەك تاختىسىنىڭ كۆرۈنۈشىنى ئالماشتۇرۇش", + "print-active-note": "نۆۋەتتىكى خاتىرىنى بېسىپ چىقىرىش", + "add-include-note-to-text": "خاتىرە ئۆز ئىچىگە ئالغان سۆزلىشىش رامكىسىنى ئېچىش", + "open-note-externally": "خاتىرە ھۆججىتىنى ئەپ ئارقىلىق ئېچىش", + "run-active-note": "نۆۋەتتىكى JavaScript (ئالدى/كەينى تەرەپ) كود خاتىرىسىنى ئىجرا قىلىش", + "toggle-note-hoisting": "خاتىرىنى يۇقىرى كۆتۈرۈش", + "unhoist": "خاتىرىنى يۇقىرى كۆتۈرۈشتىن ۋاز كېچىش", + "reload-frontend-app": "ئالدى تەرەپ ئەپىنى قايتا يۈكلەش", + "open-dev-tools": "تەتقىقاتچى قوراللىرىنى ئېچىش", + "find-in-text": "تېكىست ئىچىدىن ئىزدەش", + "toggle-left-note-tree-panel": "سول تەرەپ (خاتىرە دەرىخى) تاختىسىنى ئالماشتۇرۇش" } } From 735712123c0027165a8037af3f1920021a1e78da Mon Sep 17 00:00:00 2001 From: noobhjy Date: Thu, 16 Apr 2026 13:34:16 +0200 Subject: [PATCH 202/203] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 88.8% (1716 of 1932 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- apps/client/src/translations/cn/translation.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index ce4595d477..fccfc95676 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -97,7 +97,8 @@ "broken_relations_to_be_deleted": "将删除以下关系并断开连接 ({{ relationCount}})", "cancel": "取消", "title": "删除笔记", - "delete": "删除" + "delete": "删除", + "clones_label": "克隆" }, "export": { "export_note_title": "导出笔记", From 011cce05af8e6793825f5aa444acc32487045e8a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 16 Apr 2026 17:23:05 +0200 Subject: [PATCH 203/203] Update translation files Updated by "Cleanup translation files" add-on in Weblate. Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ --- apps/client/src/translations/de/translation.json | 3 --- apps/client/src/translations/es/translation.json | 3 --- apps/client/src/translations/ga/translation.json | 3 --- apps/client/src/translations/ja/translation.json | 3 --- 4 files changed, 12 deletions(-) diff --git a/apps/client/src/translations/de/translation.json b/apps/client/src/translations/de/translation.json index 1efdafdc6e..5f3003dddb 100644 --- a/apps/client/src/translations/de/translation.json +++ b/apps/client/src/translations/de/translation.json @@ -2238,9 +2238,6 @@ "sample_venn": "Mengen", "sample_ishikawa": "Ursache-Wirkung" }, - "revisions": { - "title": "Notizrevisionen" - }, "database": { "title": "Datenbank" }, diff --git a/apps/client/src/translations/es/translation.json b/apps/client/src/translations/es/translation.json index 2f26db1ba8..640d1af367 100644 --- a/apps/client/src/translations/es/translation.json +++ b/apps/client/src/translations/es/translation.json @@ -2239,8 +2239,5 @@ "sample_venn": "Venn", "sample_ishikawa": "Ishikawa", "sample_treemap": "Mapa de árbol" - }, - "revisions": { - "title": "Revisiones de nota" } } diff --git a/apps/client/src/translations/ga/translation.json b/apps/client/src/translations/ga/translation.json index 7326807d26..688cd23896 100644 --- a/apps/client/src/translations/ga/translation.json +++ b/apps/client/src/translations/ga/translation.json @@ -2464,9 +2464,6 @@ "text_filtered_low_confidence": "Bhraith OCR téacs le muinín {{confidence}}%, ach caitheadh leis é mar is é {{threshold}}% an tairseach íosta atá agat.", "open_media_settings": "Oscail Socruithe" }, - "revisions": { - "title": "Athbhreithnithe Nótaí" - }, "database": { "title": "Bunachar Sonraí" }, diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 3f8408c640..562c2fedf8 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -2400,9 +2400,6 @@ "text_filtered_low_confidence": "OCR は {{confidence}}% の信頼度でテキストを検出しましたが、最小しきい値が {{threshold}}% であるため、破棄されました。", "open_media_settings": "設定を開く" }, - "revisions": { - "title": "ノートの変更履歴" - }, "database": { "title": "データベース" },