A push-based hot-folder watcher that auto-registers vector files the moment another app or service drops them into a registered watch folder. A downstream pipeline writes a .geojson / .parquet / .gpkg into C:\data\feeds, and within ~2 seconds it appears on the map as a project layer — no manual Open Vector File, no app restart. The "data landing zone" workflow, fully air-gapped.
One FileSystemWatcher per enabled folder hooks OS-level change events. A 1500 ms per-file debounce collapses the burst of Created/Changed/Renamed events GIS apps emit when write-then-renaming. The debounced file flows through the existing ProjectHub.RegisterLayer + ProjectStore.AddCatalogEntry seam — no new DuckDB code, no new registration route. The watcher is strictly detection.
CREATE OR REPLACE VIEW. Per-file System.Threading.Timer, configurable via FolderWatch:DebounceMs.StartWatching re-scans each folder once to catch files that arrived while the watcher was down.arcgis-map:catalog-changed. A slow browser never stalls ingestion.FileSystemWatcher.Error (common on SMB buffer overflow) logs + schedules a SyncWatchers retry.// register a hot folder curl -X POST http://localhost:5059/api/project/watch/folders \ -H 'Content-Type: application/json' \ -d '{ "path": "C:/data/feeds", "alias": "Downstream pipeline drop", "watchSubdirs": true }' → FileSystemWatcher armed on C:/data/feeds → pipeline writes alerts.geojson → 1500 ms debounce → IngestOne → catalog row → SSE toast + layer on the map
A few deliberate boundaries keep the watcher robust and predictable. None of these are gaps to "fix" — they're the contract.
DuckDB ST_Read doesn't read KML, and the separate KML subsystem targets ground overlays + tours, not vector registration. A dropped .kml is logged + ignored.
Loose sidecar files (.shp without its .dbf/.prj) are rejected — only complete sources register.
ReconcileCatalog on project-open already marks missing sources stale=true. Live Deleted events are out of scope — you asked for new feeds, not removal tracking.
The watcher lifecycle is owned by the host's SwitchProject (via RebindFolderWatcher), not DI. Old project's watchers stop; the new project's start.
FolderScanner.IsSupported(path) is the static allowlist — case-insensitive, covering GeoJSON/Parquet/GPKG/FGB/Shapefile/Arrow/PMTiles.
observefsThe DuckDB observefs extension is an I/O latency profiler, not a directory watcher. FileSystemWatcher is the correct primitive.
| Group | Endpoint | Notes |
|---|---|---|
| Scan | POST /api/project/scan | One-shot recursive scan + dedupe of a folder |
| Folders | GET / POST / PUT / DELETE /api/project/watch/folders[/{id}] | CRUD over watch-folder connections |
| Control | POST /api/project/watch/toggle | Enable/disable the whole watcher |
| Status | GET /api/project/watch/status | { enabled, running, folders, debounceMs } |
| Events | GET /api/project/watch/events | SSE stream of FolderWatchEvent (new-layer notifications) |
Base URL http://localhost:5059. Config "FolderWatch": { DebounceMs: 1500, RescanOnReconnectMs: 30000, Enabled: true }. Bridge: window.MapApp.{toggleFolderWatchPanel, scanFolder, addWatchFolder, removeWatchFolder, toggleLiveWatch}. Documented in AGENTS.md ("Folder watcher"). Tests: tests/FolderWatchTests/smoke.cs (47 cases).
A registered watch folder closes the data landing zone — drop a file, see it live, no manual steps.