* fix(vue): release CanvasKit WebGL contexts when canvases go away GetWebGLContext registers a canvas's context in CanvasKit's global table, and the surface manager only called deleteContext on a failed setup. Every destroyed canvas, and every surface rebuilt after a resize or color-space change, stayed registered, and through the canvas element CanvasKit kept the closed editor's component tree, store, and graph alive. The manager now keeps the handle and releases it on rebuild and destroy. deleteContext also leaves CanvasKit holding the last current context, so when the released context was current, a parking context on a 1x1 offscreen canvas becomes current instead. * fix(core): uninstall an editor's text measurer when it closes setCanvasKit installed a global text measurer that closed over the editor and its renderer, and nothing uninstalled it, so the last editor to set up a canvas stayed alive after its tab closed and layout kept measuring with its destroyed renderer. installTextMeasurer returns an uninstall function; an editor uninstalls its measurer when disposed or when its last renderer goes, and the most recent measurer still installed takes over. * fix(app): give editor stores their own effect scope The first store is created during WorkspaceView's setup, so effects created while building it joined the view's scope. Their cleanups stayed registered there after the store was disposed and kept the startup document alive for the life of the app. Stores now build inside a detached effect scope that dispose stops. * fix(app): follow the active tab in app-level editor subscriptions App.vue provided a proxy that resolved to whichever store was active when a property was read, so app-level composables such as the menu and keyboard commands subscribed once to the startup store. They missed events from later documents and kept that store alive, and Undo and Redo availability came from the first document's history. The app-level editor now moves event subscriptions to the active store, and each tab's editor UI gets its own store through EditorTabScope, so per-tab components stay bound to their document when it closes. Selection capabilities read the undo history lazily instead of capturing the first store's manager. * fix(app): stop keeping closed documents in chat history and startup The chat history kept the last editor it served only to compare it with the next one; it now holds it weakly. WorkspaceView's first tab was a top-level setup binding, which Vue keeps on the instance; it is now block-scoped. * test(app): check that documents closed in tabs are released Opens a document in a new tab three times, closes each with discard, forces garbage collection, and checks that weak references to the closed graphs clear. On master all three graphs stay alive. * refactor(app): provide the tab's store from a tab-keyed editor view EditorTabScope existed only to provide a tab's store to its editor UI. WorkspaceView now keys EditorWorkspace by tab, whose contents were already remounted per tab, so the view provides the tab's store in its own setup and the wrapper component goes away. * fix(app): stop a store's effect scope when building the store throws Effects created before the throw would otherwise stay alive with the partial store, which no caller can dispose. * test(app): avoid empty callbacks in the store scope tests
65 lines
2.1 KiB
Vue
65 lines
2.1 KiB
Vue
<script setup lang="ts">
|
|
import { useHead } from '@unhead/vue'
|
|
import { useEventListener } from '@vueuse/core'
|
|
import { MotionConfig } from 'motion-v'
|
|
import { TooltipProvider } from 'reka-ui'
|
|
import { computed, onMounted } from 'vue'
|
|
|
|
import { provideEditor, useI18n } from '@open-pencil/vue'
|
|
|
|
import { useDocumentCloseProtection } from '@/app/document/close/use'
|
|
import { useFollowingEditorStore } from '@/app/editor/active-store'
|
|
import { animationsEnabled } from '@/app/shell/motion'
|
|
import { useAppTheme } from '@/app/shell/theme'
|
|
import { toast } from '@/app/shell/ui'
|
|
import { scheduleStartupUpdateCheck } from '@/app/shell/updater'
|
|
import { kickSyncEngine } from '@/app/storage/sync'
|
|
import { prepareForReload } from '@/app/tabs'
|
|
import UnsavedChangesDialog from '@/components/document/UnsavedChangesDialog.vue'
|
|
import PublishLibraryDialog from '@/components/libraries/PublishLibraryDialog.vue'
|
|
import LibraryUpdateReviewDialog from '@/components/libraries/review/LibraryUpdateReviewDialog.vue'
|
|
import RecoveryDialog from '@/components/recovery/RecoveryDialog.vue'
|
|
import SettingsDialog from '@/components/settings/SettingsDialog.vue'
|
|
import AppShell from '@/components/shell/AppShell.vue'
|
|
import Toaster from '@/components/shell/Toaster.vue'
|
|
|
|
const { updates, locale } = useI18n()
|
|
|
|
useHead({
|
|
titleTemplate: (title) => (title ? `${title} — OpenPencil` : 'OpenPencil'),
|
|
htmlAttrs: {
|
|
lang: locale,
|
|
'data-motion': computed(() => (animationsEnabled.value ? 'full' : 'off'))
|
|
}
|
|
})
|
|
|
|
provideEditor(useFollowingEditorStore())
|
|
useAppTheme()
|
|
useDocumentCloseProtection()
|
|
useEventListener(window, 'pagehide', () => {
|
|
void prepareForReload()
|
|
})
|
|
|
|
onMounted(() => {
|
|
toast.setupGlobalErrorHandler()
|
|
scheduleStartupUpdateCheck(updates)
|
|
void kickSyncEngine()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<MotionConfig :reduced-motion="animationsEnabled ? 'never' : 'always'">
|
|
<TooltipProvider :delay-duration="400">
|
|
<AppShell>
|
|
<RouterView />
|
|
</AppShell>
|
|
<SettingsDialog />
|
|
<RecoveryDialog />
|
|
<UnsavedChangesDialog />
|
|
<PublishLibraryDialog />
|
|
<LibraryUpdateReviewDialog />
|
|
<Toaster />
|
|
</TooltipProvider>
|
|
</MotionConfig>
|
|
</template>
|