Skip to content

fix: workspace watchers with Parcel watcher#1769

Merged
zerob13 merged 6 commits into
devfrom
codex/parcel-watcher-issue-1764
Jun 16, 2026
Merged

fix: workspace watchers with Parcel watcher#1769
zerob13 merged 6 commits into
devfrom
codex/parcel-watcher-issue-1764

Conversation

@zerob13

@zerob13 zerob13 commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #1764.

  • Replace the chokidar-backed workspace and skill hot-reload watchers with a shared @parcel/watcher service.
  • Add Electron utility-process watcher hosts for content and git metadata, with a main-process watcher pool that handles deduplication, ref-counting, filtering, and event fan-out.
  • Add debounced batching, overflow protection, snapshot polling fallback, host restart replay, and workspace watcher health status events.
  • Keep git metadata watching isolated in a separate host and preserve raw git event ordering for transient lockfile updates.
  • Add a compact WorkspacePanel degraded/failed watcher status banner and localized strings.
  • Update ASAR/native package handling so Parcel watcher prebuilds are copied into packaged apps.

Technical Design

  • WatcherService is the feature-facing facade. Presenters subscribe through WatchRequest objects and do not import native watcher code.
  • WatcherHostClient owns utility-process lifecycle, RPC correlation, restart status, and request replay.
  • WatcherPool deduplicates equivalent subscriptions, normalizes macOS /private/var paths, applies include/exclude filters, and fans batches out to subscribers.
  • watcherHost runs inside the utility process, owns @parcel/watcher subscriptions, coalesces regular content events, chunks large batches, and switches to snapshot polling when native watching fails or overflows.
  • The git host uses its own utility-process channel and keeps lockfile and index events observable so workspace git metadata invalidation remains responsive.

Validation

  • pnpm run format
  • pnpm run i18n
  • pnpm run lint
  • pnpm run typecheck
  • pnpm test (388 files passed, 3222 tests passed; existing skipped suites unchanged)
  • pnpm run build
  • pnpm exec playwright test -c test/e2e/playwright.config.ts test/e2e/specs/30-workspace-watcher-events.smoke.spec.ts

Summary by CodeRabbit

Release Notes

  • New Features
    • Added workspace file-watching status reporting in the UI (degraded/failed), including localized messages.
    • Added an end-to-end smoke test covering workspace watcher event delivery.
  • Bug Fixes
    • Improved filesystem watching reliability with event coalescing/buffering and automatic fallback polling when native watching isn’t available.
    • Improved workspace file search reliability via a filesystem fallback when FFF fails, with warning deduplication.
  • Documentation
    • Added implementation plan/specs for the new watcher architecture and large-workspace timeout behavior.
  • Tests / Chores
    • Updated watcher/presenter and packaging tests; updated Electron packaging to include required Parcel watcher binaries.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces chokidar-based file watching with @parcel/watcher in a utility-process architecture to eliminate file-descriptor exhaustion on large workspaces. A new FileWatcherHost runs inside an Electron utilityProcess, fronted by WatcherHostClient, WatcherPool, and FileWatcherService. Both WorkspacePresenter and SkillPresenter are migrated to the new async IFileWatcherService API. A typed workspace.watch.status.changed event surfaces degraded/failed watch health to the renderer's WorkspacePanel, with i18n strings across 20 locales and packaging support for native binaries. Also adds FFF search fallback with filesystem scanning when timeouts occur on large workspaces, and spec for skill watcher initialization resilience.

Changes

@parcel/watcher Utility-Process Watcher Migration

Layer / File(s) Summary
Spec, plan, and task docs
docs/issues/parcel-watcher-issue-1764/*
Adds spec, implementation plan, and task checklist covering architecture decisions, requirements, acceptance criteria, packaging risks, and test scope for the migration.
Shared types, domain schemas, and event contracts
src/main/lib/fileWatcher/watcherTypes.ts, src/shared/contracts/domainSchemas.ts, src/shared/contracts/events/workspace.events.ts, src/shared/contracts/events.ts, src/shared/types/presenters/workspace.d.ts, src/shared/types/presenters/index.d.ts, src/shared/types/skill.ts
Defines all watcher type primitives (WatcherHostKind, WatchMode, WatchHealth, WatchRequest, WatcherEventBatch, WatchHandle, IFileWatcherService, RPC protocol types), Zod schemas for watch health/mode/reason, the workspaceWatchStatusChangedEvent contract registered in DEEPCHAT_EVENT_CATALOG, and updated ISkillPresenter async signatures.
Build config and native binary packaging
package.json, electron.vite.config.ts, electron-builder.yml, scripts/afterPack.js
Adds @parcel/watcher@^2.5.6 dependency, registers fileWatcherUtilityHost as a Rollup entry, adds asarUnpack glob patterns, and adds copyParcelWatcherNativePackages to copy platform-specific native binaries into unpacked app resources.
FileWatcherHost and IPC utility-process entry
src/main/lib/fileWatcher/watcherHost.ts, src/main/lib/fileWatcher/fileWatcherUtilityHost.ts, src/main/fileWatcherUtilityHostEntry.ts
FileWatcherHost subscribes to @parcel/watcher, buffers/coalesces events, flushes chunked batches, handles native errors by switching to snapshot-polling fallback, and emits typed health/status messages. fileWatcherUtilityHost.ts is the IPC bridge that dispatches RPC requests to the host. The entry module guards against starting outside a utility process.
Event coalescing
src/main/lib/fileWatcher/eventCoalescer.ts
Implements coalesceWatcherEvents with platform-aware path key normalization, per-path event merging (delete→create→update; create→delete cancels), and descendant delete deduplication.
WatcherHostClient, WatcherPool, and FileWatcherService
src/main/lib/fileWatcher/watcherHostClient.ts, src/main/lib/fileWatcher/watcherPool.ts, src/main/lib/fileWatcher/watcherService.ts, src/main/lib/fileWatcher/index.ts
WatcherHostClient spawns the utility process, manages RPC, and implements restart-with-replay on unexpected exit. WatcherPool deduplicates requests by key, filters batches by include/exclude rules, and fans out events to multiple listeners over two clients (content and git). FileWatcherService provides a singleton facade with createWatcherRequestId.
WorkspacePresenter migration
src/main/presenter/workspacePresenter/index.ts, src/main/presenter/index.ts
Replaces chokidar FSWatcher instances with WatchHandle from watcherService.watch; rewrites content-batch handling for full/fs/git invalidations; adds git lock-path watching; adds emitWatchStatus; converts destroy() to async; injects watcherService via constructor.
SkillPresenter migration
src/main/presenter/skillPresenter/index.ts
Replaces chokidar with async watchSkillFiles()/stopWatching() backed by watcherService.watch; adds batch handling for create/update/delete/overflow; implements metadataCache/contentCache handlers with duplicate-name conflict resolution; converts destroy() to async.
Renderer watch-status surface
src/renderer/api/WorkspaceClient.ts, src/renderer/src/components/sidepanel/composables/useWorkspaceSync.ts, src/renderer/src/components/sidepanel/WorkspacePanel.vue, src/renderer/src/i18n/*/chat.json
Adds onWatchStatusChanged to WorkspaceClient; adds watchStatus reactive state and lifecycle wiring in useWorkspaceSync; adds a conditional watchStatusBanner computed and UI element in WorkspacePanel.vue; adds degraded/failed translation strings across 20 locales.
Parcel watcher tests
test/main/lib/fileWatcher/*.test.ts, test/main/presenter/*.test.ts, test/main/routes/contracts.test.ts, test/main/scripts/afterPack.test.ts, test/renderer/components/WorkspacePanel.test.ts, test/e2e/specs/30-workspace-watcher-events.smoke.spec.ts
Adds tests for event coalescing, WatcherPool deduplication/filtering/status routing, Parcel binary copy in afterPack, updated event catalog, refactored presenter tests via FakeWatcherService replacing chokidar mocks, updated WorkspacePanel renderer tests for watch-status UI, and a new e2e smoke test for watcher healthy/native status and invalidation events.

FFF Large-Workspace Timeout Fallback

Layer / File(s) Summary
FFF large-workspace timeout and skill watcher initialize specs
docs/issues/fff-large-workspace-timeout/spec.md, docs/issues/skill-watcher-initialize-failure/spec.md
Defines the issue, user story, acceptance criteria, and test coverage for bounded filesystem-based fallback searching when FFF scan/glob times out on large workspaces. Also documents expected behavior when the skill file watcher fails to start or exits during startup, ensuring SkillPresenter.initialize() continues without failing solely due to watcher unavailability.
FileSearcher filesystem fallback with deduplication
src/main/presenter/workspacePresenter/fileSearcher.ts
Configures FFF with UI scan timeout; adds in-memory warning deduplication keyed by workspace+error; implements glob-pattern validation and breadth-first filesystem scanning with exclusion/glob/max-file support; wraps FFF calls in try/catch that falls back to filesystem on timeout.
FileSearcher fallback test coverage
test/main/presenter/workspacePresenter/fileSearcher.test.ts
Adds test cases verifying FFF timeout → filesystem fallback transition, warning deduplication across repeated searches, and correct result merging without duplicates.

Sequence Diagram(s)

sequenceDiagram
  participant WorkspacePresenter
  participant FileWatcherService
  participant WatcherPool
  participant WatcherHostClient
  participant UtilityProcess as FileWatcherHost (utility process)
  participant Renderer as WorkspacePanel (renderer)

  WorkspacePresenter->>FileWatcherService: watch(request, onBatch, onStatus)
  FileWatcherService->>WatcherPool: watch(request, onBatch, onStatus)
  WatcherPool->>WatcherHostClient: watch(request) via IPC RPC
  WatcherHostClient->>UtilityProcess: fork + file-watcher:request watch
  UtilityProcess-->>WatcherHostClient: file-watcher:status healthy/native
  WatcherHostClient-->>WorkspacePresenter: onStatus(healthy)
  WorkspacePresenter->>Renderer: sendToAllWindows workspace.watch.status.changed

  UtilityProcess-->>WatcherHostClient: file-watcher:event-batch
  WatcherHostClient-->>WatcherPool: onBatch(batch)
  WatcherPool-->>WorkspacePresenter: filtered onBatch(batch)
  WorkspacePresenter->>WorkspacePresenter: schedule invalidation (fs/git/full)
  WorkspacePresenter->>Renderer: sendToAllWindows workspace.invalidated

  UtilityProcess--xWatcherHostClient: unexpected exit
  WatcherHostClient->>WatcherHostClient: handleHostExit → emitDegraded + scheduleRestart
  WatcherHostClient-->>WorkspacePresenter: onStatus(degraded/utility-exit)
  WorkspacePresenter->>Renderer: sendToAllWindows workspace.watch.status.changed (degraded)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • ThinkInAIXYZ/deepchat#1352: Both PRs modify WorkspacePresenter invalidation wiring and workspace watcher lifecycle; this PR directly rewrites watcher integration as part of the chokidar→watcherService migration.
  • ThinkInAIXYZ/deepchat#1266: Both PRs touch SkillPresenter hot-reload/watch behavior; this PR migrates it from chokidar to the new watcherService with async lifecycle changes.
  • ThinkInAIXYZ/deepchat#1749: Both PRs extend scripts/afterPack.js for native binary packing; the referenced PR adds FFF binary copy while this PR adds parallel Parcel Watcher native package copying.

Poem

🐇 No more EMFILE storms at dawn,
A thousand fds — thankfully gone!
@parcel/watcher takes the stage,
Utility process, its own cage.
Coalesced events flow smooth and bright,
This bunny watches files just right! 🌟

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/parcel-watcher-issue-1764

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/renderer/src/components/sidepanel/composables/useWorkspaceSync.ts (1)

421-427: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Register watch-status listeners before immediate watcher startup.

Line [397] runs ensureWatcherState(...) immediately, but the new watch-status listener is attached at Line [425]. Early workspace.watch.status.changed events can be missed, so initial degraded/failed state may never surface in the panel.

Suggested fix
-import { computed, onBeforeUnmount, onMounted, ref, watch, type ComputedRef, type Ref } from 'vue'
+import { computed, onBeforeUnmount, ref, watch, type ComputedRef, type Ref } from 'vue'

-  onMounted(() => {
-    stopWorkspaceInvalidatedListener = options.workspaceClient.onInvalidated(
-      handleWorkspaceInvalidated
-    )
-    stopWorkspaceWatchStatusListener = options.workspaceClient.onWatchStatusChanged(
-      handleWorkspaceWatchStatusChanged
-    )
-  })
+  stopWorkspaceInvalidatedListener = options.workspaceClient.onInvalidated(
+    handleWorkspaceInvalidated
+  )
+  stopWorkspaceWatchStatusListener = options.workspaceClient.onWatchStatusChanged(
+    handleWorkspaceWatchStatusChanged
+  )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/sidepanel/composables/useWorkspaceSync.ts` around
lines 421 - 427, The watch-status listener registration is happening too late in
the initialization sequence. In the onMounted hook in useWorkspaceSync.ts, the
stopWorkspaceWatchStatusListener assignment (which registers the handler for
workspace watch-status changes) needs to be moved to occur before the
ensureWatcherState function is called, as ensureWatcherState triggers the
watcher immediately and could emit status-change events before the listener is
attached, causing initial degraded or failed states to be missed by the panel.
src/renderer/src/i18n/es-ES/chat.json (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Translate watchStatus strings into native languages.

All five non-English locale files contain English placeholder text for the new workspace.files.watchStatus keys instead of native-language translations:

  • src/renderer/src/i18n/es-ES/chat.json#L264-267: translate the degraded and failed strings into Spanish.
  • src/renderer/src/i18n/fa-IR/chat.json#L214-217: translate the degraded and failed strings into Farsi/Persian.
  • src/renderer/src/i18n/fr-FR/chat.json#L214-217: translate the degraded and failed strings into French.
  • src/renderer/src/i18n/he-IL/chat.json#L214-217: translate the degraded and failed strings into Hebrew.
  • src/renderer/src/i18n/id-ID/chat.json#L264-267: translate the degraded and failed strings into Indonesian.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/i18n/es-ES/chat.json` at line 1, Replace the English
placeholder text for the workspace.files.watchStatus keys in the five
non-English locale files with native-language translations. In
src/renderer/src/i18n/es-ES/chat.json (lines 264-267), translate the degraded
and failed strings into Spanish. In src/renderer/src/i18n/fa-IR/chat.json (lines
214-217), translate the degraded and failed strings into Farsi/Persian. In
src/renderer/src/i18n/fr-FR/chat.json (lines 214-217), translate the degraded
and failed strings into French. In src/renderer/src/i18n/he-IL/chat.json (lines
214-217), translate the degraded and failed strings into Hebrew. In
src/renderer/src/i18n/id-ID/chat.json (lines 264-267), translate the degraded
and failed strings into Indonesian.

Source: Coding guidelines

src/shared/types/presenters/workspace.d.ts (1)

97-102: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep WorkspaceInvalidationEvent.version required to match the contract.

Line 101 makes version optional, but workspace.invalidated defines version as required. This weakens type safety across the shared event boundary.

Suggested fix
 export type WorkspaceInvalidationEvent = {
   workspacePath: string
   kind: WorkspaceInvalidationKind
   source: WorkspaceInvalidationSource
-  version?: number
+  version: number
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/types/presenters/workspace.d.ts` around lines 97 - 102, The
WorkspaceInvalidationEvent type marks the version field as optional with the ?
modifier, but the workspace.invalidated contract requires version to be a
non-optional required field. Remove the ? from the version property declaration
in the WorkspaceInvalidationEvent type definition to make it a required field
matching the actual contract requirement and restore type safety at the shared
event boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/issues/parcel-watcher-issue-1764/plan.md`:
- Around line 210-227: The fallback modes section documents
`git-metadata-polling` as a polling strategy, but this mode is missing from the
earlier `WatcherMode` union type definition and the status-event example that
shows the `mode` field values as `native | snapshot-polling | lifecycle`. To fix
this inconsistency, either add `git-metadata-polling` to the `WatcherMode` union
and update the status-event example to include `git-metadata-polling` as a valid
mode value, or remove `git-metadata-polling` from the Fallback modes section if
it is intended to be internal only. Whichever approach you choose, ensure that
the `WatcherMode` contract, the status-event example, and the documented
fallback modes all describe the same set of possible modes.

In `@docs/issues/parcel-watcher-issue-1764/spec.md`:
- Line 138: Remove the phrase "currently the latest npm release" from line 138
in the spec.md file. Keep only the part about the recommended dependency version
with the semantic versioning constraint "`@parcel/watcher`@^2.5.6". This
eliminates time-bound language that will become outdated when newer versions are
released, while maintaining the useful version specification information.

In `@docs/issues/parcel-watcher-issue-1764/tasks.md`:
- Around line 17-23: The verification checklist includes `pnpm run typecheck`
but the acceptance gate specification requires `pnpm run typecheck:node`. Update
the task list to use the correct command `pnpm run typecheck:node` that matches
the spec, or if both commands are required, add a note clarifying that both
should be executed by contributors.

In `@src/main/lib/fileWatcher/eventCoalescer.ts`:
- Around line 57-69: The code is using path.normalize on lines 59 and 66 to
normalize paths in the deletedParents array and when checking descendants, but
this is weaker than the normalizeEventKey normalization used elsewhere. Replace
both occurrences of path.normalize(event.path) in the deletedParents mapping and
the normalized variable assignment in the filter function with
normalizeEventKey(event.path) to ensure consistent and stronger path
normalization that prevents missing equivalent paths and leaking redundant
delete events.

In `@src/main/lib/fileWatcher/watcherHost.ts`:
- Around line 166-179: After detecting and reporting the terminal `root-deleted`
state in the root path existence check (where fs.existsSync fails for
activeWatch.request.rootPath), prevent any future polling from being scheduled
for this activeWatch. Identify the polling scheduling mechanism (likely a
timeout or interval setup) and ensure it is cancelled or prevented from being
re-scheduled once the `root-deleted` condition is detected and the status is set
to failed. This issue appears at two locations: the primary check at lines
166-179 and a secondary occurrence at lines 215-218; both sites need the same
fix to prevent polling from continuing after the terminal failure state is
reached.

In `@src/main/lib/fileWatcher/watcherHostClient.ts`:
- Around line 91-115: The request method does not have a timeout mechanism,
which means if the utility host becomes unresponsive and never replies, the
promise will hang indefinitely and leave entries in the pendingRequests Map
unresolved. Add a timeout to the promise created in the request method that
rejects after a reasonable duration (typically a few seconds). When the timeout
fires, delete the corresponding entry from pendingRequests using the id as the
key and reject the promise with an appropriate timeout error. This ensures that
hung RPC calls are cleaned up and do not block watch, unwatch, or shutdown
operations.

In `@src/main/lib/fileWatcher/watcherPool.ts`:
- Around line 108-130: When the watch promise in `entry.ready` rejects, the
entry remains in both the `entriesByKey` and `entriesByWatchId` maps, causing
future callers with the same key to inherit the failed promise and keep failing.
Add error handling around the `await entry.ready` statement to catch rejection
and clean up by removing the poisoned entry from both `entriesByKey` and
`entriesByWatchId` maps before re-throwing the error, allowing subsequent
requests with the same key to create a fresh entry and retry.

In `@src/main/lib/fileWatcher/watcherService.ts`:
- Around line 34-35: In the resetFileWatcherServiceForTests function, before
setting sharedWatcherService to null, you need to properly clean up the existing
watcher service instance to prevent watcher clients and processes from remaining
alive across tests. Add logic to check if sharedWatcherService exists and call
its cleanup method (such as destroy() or close()) before assigning null to
sharedWatcherService.

In `@src/main/presenter/skillPresenter/index.ts`:
- Around line 1955-1961: The code in this section calls discoverSkills() which
already publishes the skills.catalog.changed event internally, and then
immediately publishes the same event again at lines 1956-1960 with duplicate
data. Remove the redundant publishDeepchatEvent call for skills.catalog.changed
since the discoverSkills() method already handles this publication. Keep the
const skills assignment and the return statement.

In `@src/main/presenter/workspacePresenter/index.ts`:
- Around line 230-233: The fire-and-forget calls to
`this.refreshGitWatcher(runtime)` at lines 232 and 242 lack rejection handlers,
which can cause unhandled promise rejections. Add a `.catch()` handler to both
invocations of `refreshGitWatcher(runtime)` to properly handle any promise
rejections, ensuring errors are either logged or handled gracefully rather than
becoming unhandled rejections that could destabilize the runtime.
- Around line 168-176: The runtime is being added to the watchRuntimes map at
the beginning of the async setup sequence, before createContentWatcher and
refreshGitWatcher complete. If either of these async operations throws an error,
the runtime remains in the map but with incomplete or failed initialization,
causing subsequent watchWorkspace calls to find it in the map and only increment
refCount instead of retrying setup. Move the this.watchRuntimes.set(normalized,
runtime) call to execute only after both createContentWatcher and
refreshGitWatcher have completed successfully, ensuring the insertion is atomic
with the full initialization. This way, if setup fails at any point, the runtime
is not added to the map and can be properly retried on the next watchWorkspace
call.

In `@src/renderer/src/i18n/da-DK/chat.json`:
- Around line 214-217: The watchStatus object containing "degraded" and "failed"
messages remain in English across multiple non-English locale files. Replace the
English strings with proper translations in each file: in
src/renderer/src/i18n/da-DK/chat.json at lines 214-217 replace with Danish
translations, in src/renderer/src/i18n/de-DE/chat.json at lines 264-267 replace
with German translations, in src/renderer/src/i18n/pt-BR/chat.json at lines
214-217 replace with Brazilian Portuguese translations, in
src/renderer/src/i18n/ru-RU/chat.json at lines 214-217 replace with Russian
translations, and in src/renderer/src/i18n/tr-TR/chat.json at lines 264-267
replace with Turkish translations. For each file, translate both the
watchStatus.degraded and watchStatus.failed string values into the corresponding
target language.

In `@src/renderer/src/i18n/it-IT/chat.json`:
- Around line 264-267: The workspace watch-status strings contain English
placeholder text in five locale files instead of proper translations. Translate
both the degraded and failed keys under workspace.files.watchStatus to the
appropriate language at each location: in src/renderer/src/i18n/it-IT/chat.json
(lines 264-267) translate to Italian, in src/renderer/src/i18n/ja-JP/chat.json
(lines 214-217) translate to Japanese, in src/renderer/src/i18n/ko-KR/chat.json
(lines 214-217) translate to Korean, in src/renderer/src/i18n/ms-MY/chat.json
(lines 264-267) translate to Malay, and in src/renderer/src/i18n/pl-PL/chat.json
(lines 264-267) translate to Polish. For each locale, replace the English text
for both degraded ("Watching in fallback mode. Changes may refresh slower.") and
failed ("File watching is unavailable. Refresh or reselect the workspace.") with
their native language equivalents.

In `@src/renderer/src/i18n/vi-VN/chat.json`:
- Around line 264-266: The watchStatus strings (degraded and failed) in the
locale files are using placeholder text that was not properly localized for each
target language. In src/renderer/src/i18n/vi-VN/chat.json at lines 264-266,
replace the English watchStatus.degraded and watchStatus.failed messages with
Vietnamese-localized translations. In src/renderer/src/i18n/zh-HK/chat.json at
lines 222-224, replace the Simplified Chinese watchStatus text with proper
Traditional Chinese (Hong Kong) phrasing. In
src/renderer/src/i18n/zh-TW/chat.json at lines 222-224, replace the Simplified
Chinese watchStatus text with proper Traditional Chinese (Taiwan) phrasing.
Ensure each locale has culturally appropriate and grammatically correct
translations for both the degraded and failed status messages.

---

Outside diff comments:
In `@src/renderer/src/components/sidepanel/composables/useWorkspaceSync.ts`:
- Around line 421-427: The watch-status listener registration is happening too
late in the initialization sequence. In the onMounted hook in
useWorkspaceSync.ts, the stopWorkspaceWatchStatusListener assignment (which
registers the handler for workspace watch-status changes) needs to be moved to
occur before the ensureWatcherState function is called, as ensureWatcherState
triggers the watcher immediately and could emit status-change events before the
listener is attached, causing initial degraded or failed states to be missed by
the panel.

In `@src/renderer/src/i18n/es-ES/chat.json`:
- Line 1: Replace the English placeholder text for the
workspace.files.watchStatus keys in the five non-English locale files with
native-language translations. In src/renderer/src/i18n/es-ES/chat.json (lines
264-267), translate the degraded and failed strings into Spanish. In
src/renderer/src/i18n/fa-IR/chat.json (lines 214-217), translate the degraded
and failed strings into Farsi/Persian. In src/renderer/src/i18n/fr-FR/chat.json
(lines 214-217), translate the degraded and failed strings into French. In
src/renderer/src/i18n/he-IL/chat.json (lines 214-217), translate the degraded
and failed strings into Hebrew. In src/renderer/src/i18n/id-ID/chat.json (lines
264-267), translate the degraded and failed strings into Indonesian.

In `@src/shared/types/presenters/workspace.d.ts`:
- Around line 97-102: The WorkspaceInvalidationEvent type marks the version
field as optional with the ? modifier, but the workspace.invalidated contract
requires version to be a non-optional required field. Remove the ? from the
version property declaration in the WorkspaceInvalidationEvent type definition
to make it a required field matching the actual contract requirement and restore
type safety at the shared event boundary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dbc3bff8-cecf-4d26-b6b2-d1d43a7873f2

📥 Commits

Reviewing files that changed from the base of the PR and between 32a3538 and af6209c.

📒 Files selected for processing (56)
  • docs/issues/parcel-watcher-issue-1764/plan.md
  • docs/issues/parcel-watcher-issue-1764/spec.md
  • docs/issues/parcel-watcher-issue-1764/tasks.md
  • electron-builder.yml
  • electron.vite.config.ts
  • package.json
  • scripts/afterPack.js
  • src/main/fileWatcherUtilityHostEntry.ts
  • src/main/lib/fileWatcher/eventCoalescer.ts
  • src/main/lib/fileWatcher/fileWatcherUtilityHost.ts
  • src/main/lib/fileWatcher/index.ts
  • src/main/lib/fileWatcher/watcherHost.ts
  • src/main/lib/fileWatcher/watcherHostClient.ts
  • src/main/lib/fileWatcher/watcherPool.ts
  • src/main/lib/fileWatcher/watcherService.ts
  • src/main/lib/fileWatcher/watcherTypes.ts
  • src/main/presenter/index.ts
  • src/main/presenter/skillPresenter/index.ts
  • src/main/presenter/workspacePresenter/index.ts
  • src/renderer/api/WorkspaceClient.ts
  • src/renderer/src/components/sidepanel/WorkspacePanel.vue
  • src/renderer/src/components/sidepanel/composables/useWorkspaceSync.ts
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/de-DE/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/es-ES/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/id-ID/chat.json
  • src/renderer/src/i18n/it-IT/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/ms-MY/chat.json
  • src/renderer/src/i18n/pl-PL/chat.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/tr-TR/chat.json
  • src/renderer/src/i18n/vi-VN/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/shared/contracts/domainSchemas.ts
  • src/shared/contracts/events.ts
  • src/shared/contracts/events/workspace.events.ts
  • src/shared/types/presenters/index.d.ts
  • src/shared/types/presenters/workspace.d.ts
  • src/shared/types/skill.ts
  • test/e2e/specs/30-workspace-watcher-events.smoke.spec.ts
  • test/main/lib/fileWatcher/eventCoalescer.test.ts
  • test/main/lib/fileWatcher/watcherPool.test.ts
  • test/main/presenter/skillPresenter/skillPresenter.test.ts
  • test/main/presenter/workspacePresenter.test.ts
  • test/main/routes/contracts.test.ts
  • test/main/scripts/afterPack.test.ts
  • test/renderer/components/WorkspacePanel.test.ts

Comment thread docs/issues/parcel-watcher-issue-1764/plan.md
Comment thread docs/issues/parcel-watcher-issue-1764/spec.md Outdated
Comment thread docs/issues/parcel-watcher-issue-1764/tasks.md
Comment thread src/main/lib/fileWatcher/eventCoalescer.ts
Comment on lines +166 to +179
if (!fs.existsSync(activeWatch.request.rootPath)) {
this.enqueueEvents(activeWatch, [
{
path: activeWatch.request.rootPath,
type: 'root-deleted'
}
])
this.sendStatus(activeWatch, {
health: 'failed',
mode: activeWatch.mode,
reason: 'root-deleted'
})
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stop polling after terminal root-deleted state to avoid repeated failure churn.

When rootPath is missing, the host emits root-deleted/failed but keeps scheduling polling, which can repeatedly fire the same failure path every interval and retrigger downstream invalidation work.

Suggested fix
@@
-    void poll()
-    activeWatch.pollTimer = setInterval(() => {
+    activeWatch.pollTimer = setInterval(() => {
       void poll()
     }, SNAPSHOT_POLL_INTERVAL_MS)
+    void poll()
@@
         if (!fs.existsSync(activeWatch.request.rootPath)) {
           this.enqueueEvents(activeWatch, [
             {
               path: activeWatch.request.rootPath,
               type: 'root-deleted'
             }
           ])
           this.sendStatus(activeWatch, {
             health: 'failed',
             mode: activeWatch.mode,
             reason: 'root-deleted'
           })
+          if (activeWatch.pollTimer) {
+            clearInterval(activeWatch.pollTimer)
+            activeWatch.pollTimer = null
+          }
           return
         }

Also applies to: 215-218

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/lib/fileWatcher/watcherHost.ts` around lines 166 - 179, After
detecting and reporting the terminal `root-deleted` state in the root path
existence check (where fs.existsSync fails for activeWatch.request.rootPath),
prevent any future polling from being scheduled for this activeWatch. Identify
the polling scheduling mechanism (likely a timeout or interval setup) and ensure
it is cancelled or prevented from being re-scheduled once the `root-deleted`
condition is detected and the status is set to failed. This issue appears at two
locations: the primary check at lines 166-179 and a secondary occurrence at
lines 215-218; both sites need the same fix to prevent polling from continuing
after the terminal failure state is reached.

Comment thread src/main/presenter/workspacePresenter/index.ts
Comment thread src/main/presenter/workspacePresenter/index.ts
Comment thread src/renderer/src/i18n/da-DK/chat.json
Comment thread src/renderer/src/i18n/it-IT/chat.json
Comment thread src/renderer/src/i18n/vi-VN/chat.json Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/presenter/workspacePresenter/fileSearcher.ts (1)

56-61: 💤 Low value

Consider using Node's Dirent type directly.

The FilesystemEntry type duplicates the shape of Node's Dirent. Using the built-in type from fs would be more idiomatic and would automatically stay in sync with Node.js updates.

Suggested change
+import type { Dirent } from 'fs'
 import fs from 'fs/promises'
 import path from 'path'

Then at line 240:

-    let entries: FilesystemEntry[]
+    let entries: Dirent[]

And remove lines 56-61.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/workspacePresenter/fileSearcher.ts` around lines 56 - 61,
The custom FilesystemEntry type definition duplicates the built-in Node.js
Dirent type from the fs module. Remove the FilesystemEntry type definition and
import Dirent from Node's fs module instead. Then replace all usages of
FilesystemEntry throughout the file (including at line 240 mentioned in the
comment) with the Dirent type to use Node's idiomatic type that will
automatically stay in sync with Node.js updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/presenter/workspacePresenter/fileSearcher.ts`:
- Around line 56-61: The custom FilesystemEntry type definition duplicates the
built-in Node.js Dirent type from the fs module. Remove the FilesystemEntry type
definition and import Dirent from Node's fs module instead. Then replace all
usages of FilesystemEntry throughout the file (including at line 240 mentioned
in the comment) with the Dirent type to use Node's idiomatic type that will
automatically stay in sync with Node.js updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a9ecf96-9f1f-460f-9df6-646c7b65c67d

📥 Commits

Reviewing files that changed from the base of the PR and between 2b9820a and 09022da.

📒 Files selected for processing (3)
  • docs/issues/fff-large-workspace-timeout/spec.md
  • src/main/presenter/workspacePresenter/fileSearcher.ts
  • test/main/presenter/workspacePresenter/fileSearcher.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/issues/fff-large-workspace-timeout/spec.md

@zerob13 zerob13 changed the title Fix workspace watchers with Parcel watcher fix: workspace watchers with Parcel watcher Jun 16, 2026
@zerob13 zerob13 merged commit b31e8f6 into dev Jun 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant