Summary
Every time TropyService.init() runs for a project switch, it unconditionally replaces this.watcher with a fresh TropyWatcher instance — without ever calling .unwatch() on the previous one. The orphaned watcher's 'change' handler keeps running and reads the service's current (now-different) state at call time, so a file-system change to the old project's .tpy file after switching away can trigger a resync of whichever project is now open.
Found while auditing the ClioDeck wiki against the actual code (pass 15, brainstorm & integrations cluster, project-switch-mid-operation angle).
Current behavior (verified against code)
src/main/services/tropy-service.ts:
init(projectPath) (line ~82-86) does this.watcher = new TropyWatcher(); directly, with no prior .unwatch() call on any existing watcher — contrast with the same method's explicit await this.disposePreviousRegistry(); a few lines later, which shows the developer already applies a "clean up the previous instance first" pattern elsewhere in this exact function, just not to the watcher.
startWatching() (line ~535) sets up this.watcher.on('change', ...) — a closure over the shared TropyService singleton this, not over any project-specific snapshot.
- If a previous project's watcher was actively watching (
existingProject.autoSync === true, per the auto-start logic at line ~113-119) and the user switches to a different project, the old TropyWatcher instance is simply dropped (garbage-collectable in principle, but its underlying OS-level file watch is never explicitly torn down via .unwatch()), so it can keep firing.
- Because
this is the same shared singleton instance across projects, an orphaned old watcher's 'change' callback firing after a switch reads this.vectorStore/this.projectPath, which by then point at the new project — not the one the watcher was originally set up for.
Impact
A resource leak (an old file watcher never explicitly closed) with a spurious-trigger risk: an edit to the previous project's .tpy file (e.g. still open in the Tropy desktop app) made after switching ClioDeck to a different project could trigger an unwanted resync against the wrong (currently open) project's primary-source store.
Suggested fix
Call this.watcher?.unwatch() before reassigning this.watcher = new TropyWatcher() in init(), matching the same defensive pattern already used for the provider registry in the same method.
Evidence trail
src/main/services/tropy-service.ts — init(), watcher reassignment without prior teardown; startWatching(), the 'change' handler closure.
- Wiki page corrected in the same pass:
1.6-Tropy-Integration-Guide.md.
Summary
Every time
TropyService.init()runs for a project switch, it unconditionally replacesthis.watcherwith a freshTropyWatcherinstance — without ever calling.unwatch()on the previous one. The orphaned watcher's'change'handler keeps running and reads the service's current (now-different) state at call time, so a file-system change to the old project's.tpyfile after switching away can trigger a resync of whichever project is now open.Found while auditing the ClioDeck wiki against the actual code (pass 15, brainstorm & integrations cluster, project-switch-mid-operation angle).
Current behavior (verified against code)
src/main/services/tropy-service.ts:init(projectPath)(line ~82-86) doesthis.watcher = new TropyWatcher();directly, with no prior.unwatch()call on any existing watcher — contrast with the same method's explicitawait this.disposePreviousRegistry();a few lines later, which shows the developer already applies a "clean up the previous instance first" pattern elsewhere in this exact function, just not to the watcher.startWatching()(line ~535) sets upthis.watcher.on('change', ...)— a closure over the sharedTropyServicesingletonthis, not over any project-specific snapshot.existingProject.autoSync === true, per the auto-start logic at line ~113-119) and the user switches to a different project, the oldTropyWatcherinstance is simply dropped (garbage-collectable in principle, but its underlying OS-level file watch is never explicitly torn down via.unwatch()), so it can keep firing.thisis the same shared singleton instance across projects, an orphaned old watcher's'change'callback firing after a switch readsthis.vectorStore/this.projectPath, which by then point at the new project — not the one the watcher was originally set up for.Impact
A resource leak (an old file watcher never explicitly closed) with a spurious-trigger risk: an edit to the previous project's
.tpyfile (e.g. still open in the Tropy desktop app) made after switching ClioDeck to a different project could trigger an unwanted resync against the wrong (currently open) project's primary-source store.Suggested fix
Call
this.watcher?.unwatch()before reassigningthis.watcher = new TropyWatcher()ininit(), matching the same defensive pattern already used for the provider registry in the same method.Evidence trail
src/main/services/tropy-service.ts—init(), watcher reassignment without prior teardown;startWatching(), the'change'handler closure.1.6-Tropy-Integration-Guide.md.