perf: prevent FPS drops during navigation in FluxNoteView

- Narrow backlinks watcher from entire store to store.files only,
  reducing trigger count from ~8 to 2 per navigation
- Defer computation start by 300ms so it runs after the 250ms view
  transition animation completes
- Yield to the browser between each file iteration using
  scheduler.yield() (with setTimeout fallback) to avoid blocking frames

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Calixte
2026-04-04 11:14:02 +02:00
parent d76182b2c2
commit 52d7c84bd0

View File

@@ -14,93 +14,105 @@ import { confirmMessage } from "@/utils/notif"
const isMarkdown = (filename?: string) => filename?.endsWith(".md") ?? false const isMarkdown = (filename?: string) => filename?.endsWith(".md") ?? false
const yieldToMain = () =>
"scheduler" in globalThis
? (globalThis as unknown as { scheduler: { yield: () => Promise<void> } }).scheduler.yield()
: new Promise<void>((r) => setTimeout(r, 0))
export const useComputeBacklinks = () => { export const useComputeBacklinks = () => {
const store = useUserRepoStore() const store = useUserRepoStore()
watch(store, async () => { watch(
if (!store.userSettings?.backlink) { () => store.files,
return async () => {
} await new Promise<void>((r) => setTimeout(r, 300))
let notifiedForComputation = false if (!store.userSettings?.backlink) {
const backlinks: Map<string, Backlink[]> = new Map()
for (const file of store.files) {
if (!isMarkdown(file.path) || !file.sha) {
continue
}
const fileBacklinkId = data.generateId(DataType.BacklinkNote, file.sha)
const fileBacklink = await data.get<DataType.BacklinkNote, BacklinkNote>(
fileBacklinkId
)
if (fileBacklink) {
continue
}
if (!backlinks.has(file.sha)) {
backlinks.set(file.sha, [])
}
const { getContent } = useFile(file.sha, false)
const note = await getContent()
if (!note) {
return return
} }
const parser = new DOMParser() let notifiedForComputation = false
const htmlDoc = parser.parseFromString(note, "text/html")
const links = htmlDoc.querySelectorAll("a") const backlinks: Map<string, Backlink[]> = new Map()
for (const link of links) { for (const file of store.files) {
const href = link.getAttribute("href") ?? "" await yieldToMain()
if (isExternalLink(href) || !isMarkdown(href)) { if (!isMarkdown(file.path) || !file.sha) {
continue continue
} }
const path = resolvePath(file.path ?? "", href) const fileBacklinkId = data.generateId(DataType.BacklinkNote, file.sha)
const backlinkFile = store.files.find((file) => file.path === path) const fileBacklink = await data.get<DataType.BacklinkNote, BacklinkNote>(
fileBacklinkId
if (!backlinkFile?.sha || !backlinkFile?.path) { )
if (fileBacklink) {
continue continue
} }
const previousBacklinks = backlinks.get(backlinkFile.sha) ?? [] if (!backlinks.has(file.sha)) {
backlinks.set(file.sha, [])
if (previousBacklinks.find((bl) => bl.sha === file.sha)) {
continue
} }
if (!notifiedForComputation) { const { getContent } = useFile(file.sha, false)
notifiedForComputation = true const note = await getContent()
confirmMessage("Updating backlinks...")
if (!note) {
return
} }
backlinks.set(backlinkFile.sha, [ const parser = new DOMParser()
...previousBacklinks, const htmlDoc = parser.parseFromString(note, "text/html")
{
sha: file.sha, const links = htmlDoc.querySelectorAll("a")
title: filenameToNoteTitle(file.path ?? "")
for (const link of links) {
const href = link.getAttribute("href") ?? ""
if (isExternalLink(href) || !isMarkdown(href)) {
continue
} }
])
}
}
for (const [sha, fileBacklinks] of backlinks) { const path = resolvePath(file.path ?? "", href)
const fileBacklinkId = data.generateId(DataType.BacklinkNote, sha) const backlinkFile = store.files.find((file) => file.path === path)
const backlinkNote: BacklinkNote = {
_id: fileBacklinkId, if (!backlinkFile?.sha || !backlinkFile?.path) {
$type: DataType.BacklinkNote, continue
sha: sha, }
links: fileBacklinks
const previousBacklinks = backlinks.get(backlinkFile.sha) ?? []
if (previousBacklinks.find((bl) => bl.sha === file.sha)) {
continue
}
if (!notifiedForComputation) {
notifiedForComputation = true
confirmMessage("Updating backlinks...")
}
backlinks.set(backlinkFile.sha, [
...previousBacklinks,
{
sha: file.sha,
title: filenameToNoteTitle(file.path ?? "")
}
])
}
} }
await data.update(backlinkNote) for (const [sha, fileBacklinks] of backlinks) {
backlinkEventBus.emit({ fileSha: sha }) const fileBacklinkId = data.generateId(DataType.BacklinkNote, sha)
const backlinkNote: BacklinkNote = {
_id: fileBacklinkId,
$type: DataType.BacklinkNote,
sha: sha,
links: fileBacklinks
}
await data.update(backlinkNote)
backlinkEventBus.emit({ fileSha: sha })
}
} }
}) )
} }