Problem
The dev file watcher only watches the app's own tree, so an edit to content the app READS from outside its appDir does not trigger a reload. Concrete case found while previewing the website: website/ renders blog posts read from the REPO-ROOT blog/ directory (website/modules/blog/queries/list-posts.server.ts resolves blog/ four levels up, a sibling of website/). Editing blog/<post>.md produces no reload because blog/ is outside every watched path. On Node the node --watch set is app / components / modules / lib / actions + middleware.* (packages/cli/lib/dev-supervisor.js:51-55); the in-process fs.watch watches app.appDir recursively only (packages/server/src/dev.js ~L1545). Neither sees blog/.
Any app that reads data files (markdown content, JSON fixtures, a local data dir) from outside its appDir hits this: editing that content requires a manual browser refresh, which reads as "hot reload is broken".
Design / approach
Let an app declare EXTRA watch paths that the dev watcher includes alongside the appDir, resolved relative to the app root and allowed to point outside it. A webjs.dev.watch config array (package.json webjs block) is the natural home next to the existing webjs.dev.before / webjs.dev.parallel orchestration (#550). The in-process fs.watch loop adds one recursive watcher per declared path; a change there runs the same debounced app.rebuild() + SSE reload as an in-tree edit. (An edited content file only needs a reload + a per-request re-read, not a module re-import, so the existing rebuild path is sufficient.)
Keep it opt-in and explicit (no attempt to auto-derive read paths from the import graph, which cannot see a readFile(resolve(...)) at runtime). Then set webjs.dev.watch: ["../blog"] in website/package.json so the website's own blog previews reload.
Implementation notes (for the implementing agent)
- Where to edit:
packages/server/src/dev.js startServer() in-process watcher (~L1536-1560): today it starts ONE fsWatch(app.appDir, {recursive:true}). Read the configured extra paths (a new readDevWatchPathsFromApp(appDir) alongside the sibling readXFromApp readers, e.g. readBodyLimitsFromApp at L414) and start one additional recursive watcher per path, each funnelling into the SAME debounced rebuild(). Resolve each relative to app.appDir; allow escaping it (../blog). Reuse the single watcherAbort so all watchers tear down together.
packages/cli/lib/dev-supervisor.js: if the boot-once restart path is kept for Node (see the sibling issue), extra watch paths that need a restart would go here too; for pure content reload the in-process watcher suffices, so this file may not need changes.
website/package.json webjs block: add "dev": { ..., "watch": ["../blog"] } so the demo app that surfaced this actually benefits.
- Landmines / gotchas:
shouldIgnoreWatchPath (dev.js:1490) matches relative filenames from an fs.watch event; keep applying it per watcher so node_modules/.git/.webjs/db noise under an extra path is still ignored.
- A path outside the appDir means
event.filename is relative to THAT watch root, not the appDir; do not assume appDir-relative paths downstream.
- Do not watch huge/unbounded dirs by default; this is opt-in precisely so an app does not accidentally watch its parent repo. Validate the configured paths exist (skip missing, like the supervisor's
exists() guard) and log which extra paths are watched at boot.
- Guard against watching a path that CONTAINS the appDir (double events) or the appDir itself (redundant); de-dupe.
- Invariants to respect:
packages/ stays plain JS + JSDoc. Opt-in only, no default behaviour change for apps without the config. Prod (webjs start) does not watch, unchanged.
- Tests + docs: unit
packages/server/test/dev/* (a change under a configured extra path triggers a rebuild / SSE reload; a missing configured path is skipped without crashing; shouldIgnoreWatchPath still applies). Docs: agent-docs/configuration.md (the webjs.dev block gains watch), WebjsConfig type + the config JSON Schema, packages/cli/AGENTS.md if the webjs dev description mentions watched paths.
Acceptance criteria
Problem
The dev file watcher only watches the app's own tree, so an edit to content the app READS from outside its appDir does not trigger a reload. Concrete case found while previewing the website:
website/renders blog posts read from the REPO-ROOTblog/directory (website/modules/blog/queries/list-posts.server.tsresolvesblog/four levels up, a sibling ofwebsite/). Editingblog/<post>.mdproduces no reload becauseblog/is outside every watched path. On Node thenode --watchset isapp/components/modules/lib/actions+middleware.*(packages/cli/lib/dev-supervisor.js:51-55); the in-processfs.watchwatchesapp.appDirrecursively only (packages/server/src/dev.js~L1545). Neither seesblog/.Any app that reads data files (markdown content, JSON fixtures, a local data dir) from outside its appDir hits this: editing that content requires a manual browser refresh, which reads as "hot reload is broken".
Design / approach
Let an app declare EXTRA watch paths that the dev watcher includes alongside the appDir, resolved relative to the app root and allowed to point outside it. A
webjs.dev.watchconfig array (package.jsonwebjsblock) is the natural home next to the existingwebjs.dev.before/webjs.dev.parallelorchestration (#550). The in-processfs.watchloop adds one recursive watcher per declared path; a change there runs the same debouncedapp.rebuild()+ SSE reload as an in-tree edit. (An edited content file only needs a reload + a per-request re-read, not a module re-import, so the existing rebuild path is sufficient.)Keep it opt-in and explicit (no attempt to auto-derive read paths from the import graph, which cannot see a
readFile(resolve(...))at runtime). Then setwebjs.dev.watch: ["../blog"]inwebsite/package.jsonso the website's own blog previews reload.Implementation notes (for the implementing agent)
packages/server/src/dev.jsstartServer()in-process watcher (~L1536-1560): today it starts ONEfsWatch(app.appDir, {recursive:true}). Read the configured extra paths (a newreadDevWatchPathsFromApp(appDir)alongside the siblingreadXFromAppreaders, e.g.readBodyLimitsFromAppat L414) and start one additional recursive watcher per path, each funnelling into the SAME debouncedrebuild(). Resolve each relative toapp.appDir; allow escaping it (../blog). Reuse the singlewatcherAbortso all watchers tear down together.packages/cli/lib/dev-supervisor.js: if the boot-once restart path is kept for Node (see the sibling issue), extra watch paths that need a restart would go here too; for pure content reload the in-process watcher suffices, so this file may not need changes.website/package.jsonwebjsblock: add"dev": { ..., "watch": ["../blog"] }so the demo app that surfaced this actually benefits.shouldIgnoreWatchPath(dev.js:1490) matches relative filenames from an fs.watch event; keep applying it per watcher sonode_modules/.git/.webjs/dbnoise under an extra path is still ignored.event.filenameis relative to THAT watch root, not the appDir; do not assume appDir-relative paths downstream.exists()guard) and log which extra paths are watched at boot.packages/stays plain JS + JSDoc. Opt-in only, no default behaviour change for apps without the config. Prod (webjs start) does not watch, unchanged.packages/server/test/dev/*(a change under a configured extra path triggers a rebuild / SSE reload; a missing configured path is skipped without crashing;shouldIgnoreWatchPathstill applies). Docs:agent-docs/configuration.md(thewebjs.devblock gainswatch),WebjsConfigtype + the config JSON Schema,packages/cli/AGENTS.mdif thewebjs devdescription mentions watched paths.Acceptance criteria
webjs.dev.watch: ["../blog"]set, editing a file underblog/while runningwebjs devinwebsite/reloads the browser automatically.shouldIgnoreWatchPathstill filters noise under the extra paths.agent-docs/configuration.md+WebjsConfig+ JSON Schema updated.