@lamemind/loom-deck 0.69.0 → 0.70.0

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.
package/dist/hooks.js CHANGED
@@ -8,8 +8,8 @@ import { statSync } from 'node:fs';
8
8
  import { loadTasks, loadTaskDetail } from './tasks.js';
9
9
  import { discoverProjectSessions } from './sessions.js';
10
10
  import { discoverLiveSessions, liveSig } from './live-sessions.js';
11
- import { appendSessionMeta, loadSessionIndex } from './task-index.js';
12
- import { sidecarGaps } from './session-meta.js';
11
+ import { loadSessionIndex } from './task-index.js';
12
+ import { fillSidecarGaps } from './session-meta.js';
13
13
  import { archivableIds, SCAN_INTERVAL_MS } from './archivable.js';
14
14
  import { commitTimes } from './commit-times.js';
15
15
  import { scanEpicHierarchy, EMPTY_EPIC_HIERARCHY } from './epic-hierarchy.js';
@@ -213,23 +213,14 @@ export function useSessions(projectRoot, core) {
213
213
  // Gli append NON entrano nella signature e non producono un re-render: il
214
214
  // deck il titolo lo ha dalla `Session`, che è la fonte. Questi campi
215
215
  // esistono per un lettore che non può aprire un transcript, cioè compass.
216
- try {
217
- for (const gap of sidecarGaps({
218
- pinned,
219
- sessions,
220
- bindings,
221
- titles: index.titles,
222
- models: index.models,
223
- core,
224
- })) {
225
- appendSessionMeta(projectRoot, gap.sessionId, gap);
226
- }
227
- }
228
- catch {
229
- // sidecar non scrivibile: il poll continua, il buco si riprova al giro
230
- // dopo. Niente a schermo — è manutenzione di un campo di servizio, non
231
- // un'azione che l'utente ha chiesto.
232
- }
216
+ fillSidecarGaps(projectRoot, {
217
+ pinned,
218
+ sessions,
219
+ bindings,
220
+ titles: index.titles,
221
+ models: index.models,
222
+ core,
223
+ });
233
224
  // La signature copre anche fork, pin, note e marca di priorità: un record
234
225
  // di lineage, un toggle di pin, una nota appena scritta o un 🚨 acceso
235
226
  // cambiano la lista renderizzata, quindi devono forzare il re-render come
@@ -8,14 +8,18 @@
8
8
  // lì da chi ce l'ha in mano, cioè il deck, che a ogni tick materializza le
9
9
  // `Session` complete di titolo e modello.
10
10
  //
11
- // Modulo PURO come `session-list.ts`: nessun I/O, nessun import da ink/react.
12
- // Decide COSA appendere e ritorna i record; il chiamante li scrive. La
13
- // separazione è ciò che rende testabile la parte non ovvia — la convergenza:
14
- // un derivato che non combacia mai col valore su disco produrrebbe un append a
15
- // ogni tick del poll, per sempre, senza nessun errore a dirlo.
11
+ // Il giudizio è separato dalla scrittura: `sidecarGaps` decide e ritorna i
12
+ // record, `fillSidecarGaps` li scrive. La separazione è ciò che rende testabile
13
+ // la parte non ovvia — la convergenza: un derivato che non combacia mai col
14
+ // valore su disco produrrebbe un append a ogni tick del poll, per sempre, senza
15
+ // nessun errore a dirlo.
16
+ //
17
+ // Nessun import da ink/react: il chiamante è un hook, questo modulo no.
16
18
  import { modelAlias } from './glyphs.js';
17
19
  import { stripProjectCore, stripTaskId } from './session-list.js';
18
20
  import { NO_TITLE } from './sessions.js';
21
+ import { NO_SPAWN } from './spawn.js';
22
+ import { appendSessionMeta } from './task-index.js';
19
23
  /**
20
24
  * Cap del titolo scritto nel sidecar, in CODE POINT.
21
25
  *
@@ -62,28 +66,6 @@ export function sidecarTitle(s, core, taskId) {
62
66
  return s.title === NO_TITLE ? '' : capTitle(s.title);
63
67
  return capTitle(stripTaskId(stripProjectCore(s.title, core), taskId));
64
68
  }
65
- /**
66
- * I buchi da riempire in questo giro: per ogni conversazione PINNATA, i campi
67
- * derivati che nel sidecar mancano o non corrispondono più al valore corrente.
68
- *
69
- * Solo le pinnate, e non tutte le conversazioni del progetto, perché sono le
70
- * sole che un lettore esterno mostra: il resto sarebbe churn su un file
71
- * append-only, un record per conversazione per ogni deck che si apre.
72
- *
73
- * Tre cose che non producono un append, e nessuna delle tre è un errore:
74
- * - una pinnata STALE (il transcript non c'è più) — nessuna `Session`, quindi
75
- * nessun valore da cui derivare;
76
- * - un titolo derivato VUOTO — è l'assenza di P1, e appenderlo scriverebbe la
77
- * cancellazione di un campo che nessuno ha chiesto di cancellare;
78
- * - un modello non riconducibile a un alias (`''` o fuori famiglia) — chi
79
- * riprende cadrà sulla propria cascata, che è meglio di un alias inventato.
80
- *
81
- * La CONVERGENZA è l'invariante da tenere: il confronto avviene fra il valore
82
- * derivato e il valore letto dal sidecar senza riscritture in mezzo (nessuna
83
- * sanificazione in lettura, cap applicato da questo lato una volta sola),
84
- * quindi un buco riempito resta riempito e il giro successivo non trova niente
85
- * da fare.
86
- */
87
69
  export function sidecarGaps(input) {
88
70
  const { pinned, sessions, bindings, titles, models, core } = input;
89
71
  if (pinned.size === 0)
@@ -106,3 +88,39 @@ export function sidecarGaps(input) {
106
88
  }
107
89
  return out;
108
90
  }
91
+ /**
92
+ * Scrive i buchi di questo giro. Ritorna quanti record ha appeso — zero è
93
+ * l'esito normale a regime, non un fallimento.
94
+ *
95
+ * `NO_SPAWN` frena anche questa scrittura, ed è lo stesso freno delle tab
96
+ * Ptyxis per la stessa ragione: il gate su pseudo-terminale avvia il DECK VERO
97
+ * con cwd la project root del cappello, quindi senza freno ogni run della suite
98
+ * appende record nel sidecar reale di chi la lancia. Il valore scritto sarebbe
99
+ * pure corretto — è quello che il deck scriverebbe comunque — ma è una
100
+ * scrittura fuori dalla sandbox del test, che è precisamente ciò che il freno
101
+ * esiste per impedire.
102
+ *
103
+ * Il freno sta QUI e non nel chiamante perché questa è l'unica sede della
104
+ * scrittura: nel chiamante sarebbe una guardia che il prossimo scrittore non
105
+ * vede.
106
+ *
107
+ * Un sidecar non scrivibile non è un errore da mostrare: il giro dopo riprova,
108
+ * e nel frattempo il deck funziona identico — questi campi li legge un altro
109
+ * processo, non lui.
110
+ */
111
+ export function fillSidecarGaps(projectRoot, input) {
112
+ if (NO_SPAWN)
113
+ return 0;
114
+ let scritti = 0;
115
+ try {
116
+ for (const gap of sidecarGaps(input)) {
117
+ appendSessionMeta(projectRoot, gap.sessionId, gap);
118
+ scritti++;
119
+ }
120
+ }
121
+ catch {
122
+ // sidecar non scrivibile: si riprova al giro dopo, niente a schermo — è
123
+ // manutenzione di un campo di servizio, non un'azione che l'utente ha chiesto.
124
+ }
125
+ return scritti;
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.69.0",
3
+ "version": "0.70.0",
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": {