From db162e54afcc81778bb8ccd0bfc080c7974170ac Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 7 Jun 2026 11:37:28 +0000 Subject: [PATCH] fix(typst): correct auto-compile default and debounce detection for Typst projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous implementation used useState() to detect the project type, but the file tree is loaded asynchronously after the WebSocket joinProject event, so pathInFolder() always returns null on the initial render. Use useEffect() instead — it re-runs when getRootDocInfo's reference changes (i.e. when the file tree populates), correctly detecting .typ root docs. Also adds updateAutoCompileDebounce() to DocumentCompiler so the tight debounce can be applied at that point. Co-Authored-By: Claude Sonnet 4.6 --- .../js/features/pdf-preview/util/compiler.ts | 11 +++++++ .../shared/context/local-compile-context.tsx | 29 ++++++++++++------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/services/web/frontend/js/features/pdf-preview/util/compiler.ts b/services/web/frontend/js/features/pdf-preview/util/compiler.ts index d4695558e9..bd9eef3a40 100644 --- a/services/web/frontend/js/features/pdf-preview/util/compiler.ts +++ b/services/web/frontend/js/features/pdf-preview/util/compiler.ts @@ -260,4 +260,15 @@ export default class DocumentCompiler { ) { this.defaultOptions[option] = value } + + updateAutoCompileDebounce(debounceMs: number, maxWaitMs: number) { + this.debouncedAutoCompile.cancel() + this.debouncedAutoCompile = debounce( + () => { + this.compile({ isAutoCompileOnChange: true }) + }, + debounceMs, + { maxWait: maxWaitMs } + ) + } } diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx index 8e2ed0242e..2ef1f08153 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -155,11 +155,6 @@ export const LocalCompileProvider: FC = ({ const { findEntityByPath } = useFileTreePathContext() const getRootDocInfo = useRootDoc() - // Captured once at mount; drives auto-compile defaults and debounce timing. - const [isTypstProject] = useState(() => - getRootDocInfo().rootResourcePath.endsWith('.typ') - ) - // whether a compile is in progress const [compiling, setCompiling] = useState(false) @@ -264,10 +259,10 @@ export const LocalCompileProvider: FC = ({ const [position, setPosition] = usePdfScrollPosition(lastCompileRootDocId) - // whether autocompile is switched on (Typst projects default to enabled) + // whether autocompile is switched on const [autoCompile, setAutoCompile] = usePersistedState( `autocompile_enabled:${projectId}`, - isTypstProject, + false, { listen: true } ) @@ -340,10 +335,6 @@ export const LocalCompileProvider: FC = ({ signal, openDocs, getRootDocInfo, - // Typst compiles are near-instant via `typst watch`; use a much tighter - // debounce so the PDF refreshes as the user types. - autoCompileDebounce: isTypstProject ? 300 : undefined, - autoCompileMaxWait: isTypstProject ? 1000 : undefined, }) }) @@ -352,6 +343,22 @@ export const LocalCompileProvider: FC = ({ compiler.getRootDocInfo = getRootDocInfo }, [compiler, getRootDocInfo]) + // Typst projects compile near-instantly via `typst watch`. + // getRootDocInfo() returns accurate paths only after the file tree loads + // (project joins via WebSocket), so we do detection in an effect that re-runs + // when its reference changes. window.localStorage is checked directly to + // distinguish "no stored preference" from an explicit user choice. + useEffect(() => { + const { rootResourcePath } = getRootDocInfo() + if (!rootResourcePath.endsWith('.typ')) return + // Default auto-compile ON for Typst — only if user has never set a pref. + if (window.localStorage.getItem(`autocompile_enabled:${projectId}`) === null) { + setAutoCompile(true) + } + // Tighten the debounce so the PDF updates as the user types. + compiler.updateAutoCompileDebounce(300, 1000) + }, [compiler, getRootDocInfo, projectId, setAutoCompile]) + // keep draft setting in sync with the compiler useEffect(() => { compiler.setOption('draft', draft)