Skip to content

watch new top-level entries, skip relocated cache dir - #346

Merged
raphamorim merged 4 commits into
oscar/start-watch-app-rootfrom
rapha/watch-coverage
Oct 10, 2026
Merged

raphamorim merged 4 commits into
oscar/start-watch-app-rootfrom
rapha/watch-coverage

Conversation

@raphamorim

@raphamorim raphamorim commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-ups from reviewing #345, stacked on its branch (retarget to main once it merges). Three coverage gaps in the dev watcher, each checked against Vite's watcher setup.

Top-level entries created after boot were never watched. watch_root read the root's entries once and watched each one, but never the root itself, so a root-level file or a freshly mkdir'd top-level directory (an agent adding a shared/ tree to a running server) served fine and then went stale on every later edit until restart. Vite does not have this gap because chokidar watches the root and picks up new children. The root is now watched non-recursively; a directory created under it is adopted with a recursive watch, and the files it already holds are reported as creates (chokidar emits add for each, and writes landing between the mkdir and the watch would otherwise be lost), to the batch and to the WatchFeed alike. Plain top-level files no longer get an individual watch when the root watch is up, since the pair would deliver every edit twice (ContentChanges only drops metadata noise); symlinked entries keep their own watch, which follows to the target inode the root watch never sees. A directory RENAMED into the root (a staged tree moved into place, the scaffolder shape) arrives as Modify(Name) rather than Create on both inotify and FSEvents, so adoption matches rename events too; chokidar raises addDir for both shapes.

A relocated cache dir fed its writes back into the watcher. The unwatched-dirs list is name-based (.oj-cache), so an OJ_CACHE_DIR pointing inside the root under another name was watched, and every compile's cache writes echoed back as events; under Start that is a rebundle loop. Vite puts the resolved cacheDir in chokidar's ignored (resolveChokidarOptions). The resolved cache base (canonicalized, so relative and symlinked spellings match the watcher's absolute paths) is now excluded in watch_root, in the batch filter and in WatchFeed::publish.

The Start feed subscription left a boot window. start_dev subscribed after tokio::join!(bundle, built_task), but the server's watcher is live the moment build_app returns, which can be well before the initial client bundle finishes; events in that window were published to zero subscribers and dropped. The subscription now happens inside the spawned build task, right after build_app resolves.

Test

e2e/watch-new-root-dir.mjs drives three shapes end to end: a root-level file created after boot, then edited; a top-level directory mkdir'd after boot, a file inside it served, then edited; a staged directory renamed into the root, a file inside it served, then edited. Red-proven against an unpatched binary: each post-creation edit never reloads (timeout at the edit step); with the fix each reloads promptly. The feed unit test now also pins the cache-dir exclusion. config-restart (a .env edit is a top-level file, now covered by the root watch alone), hmr-protocol and start-cloudflare-dev pass locally.

Not addressed here, noted in the #345 review: gating the Start rebundle on the previous bundle's input closure instead of rebundling on any relevant change. That is a behavior change with its own trade-offs (SSR-only edits and the reload path), coming separately.

@raphamorim
raphamorim requested a review from a team as a code owner October 9, 2026 22:44
@raphamorim
raphamorim merged commit 8404fd9 into oscar/start-watch-app-root Oct 10, 2026
5 of 7 checks passed
@raphamorim
raphamorim deleted the rapha/watch-coverage branch October 10, 2026 09:16
raphamorim added a commit that referenced this pull request Oct 10, 2026
* start dev: invalidate the worker for edits outside src/

Start mode ran a second file watcher on <root>/src only, and the worker
invalidation, the lazy runner's dirty flag and the client rebundle hung off
it. An edit outside src/ reached the browser through oj_server's watcher but
never the worker, which kept rendering the old module until a restart.

The Start watcher thread now subscribes to oj_server's watcher (WatchFeed),
published from the notify callback so it adds no debounce and never waits on
the watcher thread's plugin-hook RPCs. watch_relevant also skips the route
generator's .tanstack/ temp files and declaration files, which a root-wide
watch now sees.

* adopt resolved server.watch.ignored from vite config (#350)

* adopt resolved server.watch.ignored from vite config

* dump server log and page loads on playground failure

* log which paths trigger a start rebuild

* watch new top-level entries, skip relocated cache dir (#346)

* watch new top-level entries, skip relocated cache dir

* exclude Vite config-loader temp files from watching

* adopt dirs renamed into the root, not just created

* gate Start rebundle and reload on served module graphs (#349)

---------

Co-authored-by: Raphael Amorim <rapha850@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant