@lamemind/loom-deck 0.57.1 → 0.57.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -361,6 +361,13 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, setNote, }) {
361
361
  const next = rightPane === 'inbox' ? 'sessions' : 'inbox';
362
362
  setRightPane(next);
363
363
  setFocus((f) => (f === 'tasks' ? f : next));
364
+ // Montare il pane RIMISURA la coda. Il periodico resta, come rete per il
365
+ // deck lasciato aperto sul pane inbox, ma non è più l'unico canale: senza
366
+ // questa chiamata chi apre il pane guarda l'ultima misura, che può essere
367
+ // vecchia quanto l'intero intervallo. Non serve il verso opposto — tornare
368
+ // alle sessioni non guarda la coda, e uno scan lì si pagherebbe per nessuno.
369
+ if (next === 'inbox')
370
+ inbox.scan();
364
371
  setNote(next === 'inbox' ? '^B → pane inbox' : '^B → pane sessioni');
365
372
  }
366
373
  /** `esc` in vista normale: torna alle sessioni. Solo in quel verso — `esc`
package/dist/hooks.js CHANGED
@@ -10,7 +10,7 @@ import { discoverProjectSessions } from './sessions.js';
10
10
  import { discoverLiveSessions, liveSig } from './live-sessions.js';
11
11
  import { loadSessionIndex } from './task-index.js';
12
12
  import { archivableIds, SCAN_INTERVAL_MS } from './archivable.js';
13
- import { scanInbox } from './inbox.js';
13
+ import { scanInbox, INBOX_SCAN_INTERVAL_MS } from './inbox.js';
14
14
  import { mixedCount, readWrapCache, runWrapScan, wrapCacheFile, wrapCount, } from './wrap-scan.js';
15
15
  import { purgeTargets } from './purge.js';
16
16
  import { POLL_MS } from './model.js';
@@ -219,28 +219,53 @@ export function useDirtyFolders(idsSig, tasksDir, projectRoot) {
219
219
  return dirty;
220
220
  }
221
221
  export function useInboxScan(projectRoot, docsRoot) {
222
- const [state, setState] = useState({ files: [], ok: true, scanned: false });
222
+ const [state, setState] = useState({
223
+ files: [],
224
+ ok: true,
225
+ scanned: false,
226
+ });
227
+ const [epoch, setEpoch] = useState(0);
228
+ // Il flag di corsa sta in un ref e non in stato, come in `useWrapScan`: due
229
+ // aperture ravvicinate del pane girano nello stesso tick di React, e un
230
+ // valore di stato arriverebbe al render dopo — cioè troppo tardi per
231
+ // impedire il secondo spawn.
232
+ const busy = useRef(false);
233
+ // `alive` vive fuori dall'effect perché `run` sopravvive al suo giro: la
234
+ // richiesta on-demand è scatenata da un gesto, non dall'effect che la ospita.
235
+ const alive = useRef(true);
223
236
  useEffect(() => {
224
- let alive = true;
225
- const scan = () => {
226
- scanInbox(projectRoot, docsRoot).then((res) => {
227
- if (!alive)
228
- return;
229
- // Un tentativo fallito NON butta via l'esito dell'ultimo riuscito: la
230
- // lista precedente resta vera e ancora apribile, e il guasto si dice
231
- // col glifo di allerta accanto ai contatori. È la stessa regola già
232
- // scritta per il project status (`finish` non riscrive la cache).
233
- setState((prev) => res.ok ? { files: res.files, ok: true, scanned: true } : { ...prev, ok: false, scanned: true });
234
- });
235
- };
236
- scan();
237
- const id = setInterval(scan, SCAN_INTERVAL_MS);
237
+ alive.current = true;
238
238
  return () => {
239
- alive = false;
240
- clearInterval(id);
239
+ alive.current = false;
241
240
  };
241
+ }, []);
242
+ const run = useCallback(() => {
243
+ if (busy.current)
244
+ return;
245
+ busy.current = true;
246
+ scanInbox(projectRoot, docsRoot).then((res) => {
247
+ busy.current = false;
248
+ if (!alive.current)
249
+ return;
250
+ // Un tentativo fallito NON butta via l'esito dell'ultimo riuscito: la
251
+ // lista precedente resta vera e ancora apribile, e il guasto si dice
252
+ // col glifo di allerta accanto ai contatori. È la stessa regola già
253
+ // scritta per il project status (`finish` non riscrive la cache).
254
+ setState((prev) => res.ok ? { files: res.files, ok: true, scanned: true } : { ...prev, ok: false, scanned: true });
255
+ });
242
256
  }, [projectRoot, docsRoot]);
243
- return state;
257
+ useEffect(() => {
258
+ run();
259
+ const id = setInterval(run, INBOX_SCAN_INTERVAL_MS);
260
+ return () => clearInterval(id);
261
+ }, [run, epoch]);
262
+ // Lo scan a richiesta RESETTA il timer, come quello del wrap: senza,
263
+ // l'intervallo continuerebbe a scorrere dall'ultimo tick automatico e potrebbe
264
+ // rimisurare pochi istanti dopo un dato appena misurato.
265
+ const scan = useCallback(() => {
266
+ setEpoch((e) => e + 1);
267
+ }, []);
268
+ return { ...state, scan };
244
269
  }
245
270
  export function useWrapScan(projectRoot) {
246
271
  const path = useMemo(() => wrapCacheFile(projectRoot), [projectRoot]);
package/dist/inbox.js CHANGED
@@ -16,6 +16,18 @@ import { basename, join } from 'node:path';
16
16
  import { pluginScript } from './plugin-cache.js';
17
17
  const execFileAsync = promisify(execFile);
18
18
  export const INBOX_METRICS_SCRIPT = 'scripts/docs/doc-metrics.sh';
19
+ /**
20
+ * Ogni 30 minuti — cadenza propria, staccata dai 6h di `SCAN_INTERVAL_MS`.
21
+ *
22
+ * Gli altri tre scan periodici misurano dati che si muovono al ritmo del
23
+ * calendario (l'età di una task) o di una riscrittura di massa (l'hard-wrap dei
24
+ * `.md`). La coda inbox si muove invece a ogni checkpoint: un file nuovo appare
25
+ * quando una sessione trasloca le sue nozioni, e con la cadenza a 6h il
26
+ * contatore resterebbe fermo per l'intera mattina di lavoro che lo ha riempito.
27
+ * Il costo di un giro resta uno spawn di `doc-metrics.sh` su una cartella di
28
+ * pochi file, quindi trascurabile anche a questa frequenza.
29
+ */
30
+ export const INBOX_SCAN_INTERVAL_MS = 30 * 60 * 1000;
19
31
  /** Le tre nature che una skill sa consumare, più il file che non si parsa. */
20
32
  export const NATURE = ['nozioni', 'derivazione', 'sweep'];
21
33
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.57.1",
3
+ "version": "0.57.3",
4
4
  "description": "Deck TUI Ink per-progetto della famiglia loom: legge tasks.md e spawna sessioni Claude Code bound via LOOM_TASK",
5
5
  "type": "module",
6
6
  "bin": {