feat(nouser+sidecar): watcher con debounce 150ms + re-publish al broker
Cierra los dos pendientes documentados en 487c457: el spam de eventos
duplicados de notify y la falta de propagación al broker cuando una
Mónada cambia composición.
SidecarPool ahora es idempotente respecto a Card.id: spawn rastrea un
HashMap<Ulid, AbortHandle> y aborta la sesión previa si el id ya
existía. Nuevo drop_session(id) para cerrar Mónadas que desaparecen y
live_sessions() para introspección.
Watcher reorganizado en dos threads: dispatcher filtra notify a un
canal de paths; coordinator agrupa con HashMap<PathBuf, Instant> y
dispara batch sólo cuando todos llevan ≥150ms quietos. Cada batch
re-scanea + re-clusteriza con hidratación + diffea contra prior:
removidas → drop_session, nuevas o con composición distinta → spawn
(que reemplaza la sesión previa). Re-scan global por batch es
deliberado y O(N archivos) — aceptable hasta que duela.
This commit is contained in:
@@ -6,6 +6,48 @@ ratio/diff ver `git show <sha>`.
|
||||
|
||||
## 2026-05-09
|
||||
|
||||
### feat(nouser+sidecar): watcher con debounce + re-publish al broker
|
||||
Cierra las dos limitaciones del watcher previo: ya no spamea N veces por
|
||||
una sola edición, y el broker ve los cambios estructurales en lugar de
|
||||
quedarse con manifests congelados al arranque.
|
||||
|
||||
$ nouser daemon /tmp/x &
|
||||
$ touch /tmp/x/src/a.rs /tmp/x/src/b.rs /tmp/x/src/c.rs
|
||||
# daemon log (un solo batch, no 9 reacciones):
|
||||
[watcher] ⚙ batch: 6 path(s) coalescidos → re-scan
|
||||
[watcher] ✦ x/src nace (3 miembros, lens=Code)
|
||||
[watcher] ⌃ delta: 1 nuevas, 0 refrescadas, 0 cerradas — 3 sesiones vivas
|
||||
|
||||
Mecánica del debounce (150ms):
|
||||
- `spawn_fs_watcher` arma dos threads: **dispatcher** filtra eventos
|
||||
notify Create/Modify/Remove a un canal de paths; **coordinator**
|
||||
mantiene `HashMap<PathBuf, Instant>` y dispara batch sólo cuando
|
||||
todos los paths llevan ≥150ms quietos.
|
||||
- Un `:w` típico de vim (~5 eventos por archivo) colapsa a 1 batch.
|
||||
|
||||
Mecánica del re-publish:
|
||||
- `SidecarPool` ahora trackea `HashMap<Ulid, AbortHandle>` indexado
|
||||
por `Card.id`. Llamar `pool.spawn(card)` con un id ya presente
|
||||
aborta la sesión previa y abre una nueva — `spawn` se vuelve
|
||||
idempotente: re-publicar una Mónada cuya composición cambió
|
||||
refresca su sesión en el broker sin dejar zombies.
|
||||
- Nueva API `pool.drop_session(id)` para cerrar una sesión
|
||||
explícitamente cuando una Mónada desaparece (directorio quedó
|
||||
bajo `min_files` o se borró).
|
||||
- `pool.live_sessions()` para introspección/logs.
|
||||
- `process_change_batch` re-scanea + re-clusteriza con hidratación,
|
||||
diffea contra prior_monads, y para cada Mónada decide:
|
||||
- removida → `drop_session`
|
||||
- nueva → `spawn` con ✦
|
||||
- composición cambió (members o centroid distintos) → `spawn` con ↻
|
||||
- idéntica → no-op
|
||||
|
||||
Trade-off aceptado: re-scan global por batch (no incremental). Es
|
||||
O(N archivos) por evento y para árboles típicos (<10k) cae en
|
||||
<100ms. Optimizar a re-cluster parcial cuando duela.
|
||||
|
||||
Tests: workspace completo verde.
|
||||
|
||||
### feat(nouser): notify watcher — el sistema reacciona en tiempo real
|
||||
El daemon ahora monta un `notify::recommended_watcher` recursivo
|
||||
sobre el directorio. Cada `Create`/`Modify` de archivo regular
|
||||
|
||||
Reference in New Issue
Block a user