@lamemind/loom-deck 0.66.1 → 0.67.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/actions.js CHANGED
@@ -24,6 +24,8 @@ import { cut, cutMiddle } from './width.js';
24
24
  import { saveView, viewFilePath } from './view-store.js';
25
25
  import { onInTabCommand, runLaunch, spawnClaudeEmpty, spawnBare, spawnDeck, spawnDeckFork, spawnDeckResume, spawnTerminal, fallbackTitle, DECK_RUN, MODEL_DEFAULT, } from './spawn.js';
26
26
  import { useTaskOps } from './task-ops.js';
27
+ import { inboxTitle } from './inbox.js';
28
+ import { wrapTitle } from './wrap-scan.js';
27
29
  export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNote, }) {
28
30
  // La riga di stato di OGNI spawn di sessione Claude: il comando esatto, come
29
31
  // lo si scriverebbe in bash, invece di una parafrasi.
@@ -333,9 +335,18 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
333
335
  * Sessione NUDA e modello esplicito: il drain lavora sulla doc, non sulla
334
336
  * task (D10 preflight), e `MODEL_DEFAULT` passa nell'argv anche essendo il
335
337
  * default, come `permissionMode`.
338
+ *
339
+ * Il TITOLO lo deriva `inboxTitle` dal file, e serve un `sessionId` pinnato
340
+ * per scriverlo nel sidecar — senza, la nota non avrebbe una chiave e la riga
341
+ * in lista resterebbe nuda mentre la tab porta un nome. Le due scritture
342
+ * stanno qui accanto per la stessa ragione di `spawnForTask`: il nome che le
343
+ * due superfici mostrano nasce da un valore solo.
336
344
  */
337
345
  function drainInbox(file, prompt) {
338
- const spawned = spawnBare(cwd, prompt, MODEL_DEFAULT);
346
+ const sid = randomUUID();
347
+ const title = inboxTitle(file);
348
+ appendNote(cwd, sid, title);
349
+ const spawned = spawnBare(cwd, prompt, MODEL_DEFAULT, sid, title);
339
350
  spawned.child.on('error', () => setNote(`⚠ drain ${file.basename} fallito (${DECK_RUN})`));
340
351
  noteSpawn(spawned);
341
352
  }
@@ -349,7 +360,10 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
349
360
  * decidere se committarlo — non progettare niente.
350
361
  */
351
362
  function unwrapPath(path, prompt) {
352
- const spawned = spawnBare(cwd, prompt, 'sonnet');
363
+ const sid = randomUUID();
364
+ const title = wrapTitle(path);
365
+ appendNote(cwd, sid, title);
366
+ const spawned = spawnBare(cwd, prompt, 'sonnet', sid, title);
353
367
  spawned.child.on('error', () => setNote(`⚠ srotolamento ${path} fallito (${DECK_RUN})`));
354
368
  noteSpawn(spawned);
355
369
  }
package/dist/inbox.js CHANGED
@@ -192,6 +192,31 @@ export function inboxPrompt(f) {
192
192
  }
193
193
  return `/loom-works:${DRAIN_SKILL[f.natura]} ${f.basename}`;
194
194
  }
195
+ /** Prefisso del titolo di una sessione aperta su un file inbox. Astrale come le
196
+ * emoji delle azioni su task (`ACTION_EMOJI` in `src/spawn.ts`) e per la stessa
197
+ * ragione: `sanitize` di `src/width.ts` sostituisce con `·` ogni glifo su cui
198
+ * le due contabilità di larghezza divergono, e le emoji del BMP divergono
199
+ * sempre. Sta anche nella whitelist di `_sane_note` in `deck-run`, o arriverebbe
200
+ * nella tab ridotta a niente mentre la lista del deck la mostra. */
201
+ const INBOX_EMOJI = '🧹';
202
+ /**
203
+ * Il titolo della sessione che il deck apre su un file inbox: `🧹 <basename>`,
204
+ * senza l'estensione.
205
+ *
206
+ * Il `.md` si toglie QUI e non lo si lascia cadere a valle: l'alfabeto di
207
+ * `_sane_note` non ha il punto, quindi il titolo della tab uscirebbe con le due
208
+ * lettere saldate al nome (`…-prioritariamd`) mentre la lista del deck, che
209
+ * legge la nota grezza dal sidecar, mostrerebbe il nome intero — la stessa
210
+ * conversazione con due nomi, senza errore.
211
+ *
212
+ * Uno per ogni natura, `malformato` compreso: quella sessione ripara il marker
213
+ * invece di drenare, ma l'oggetto su cui lavora è lo stesso file, ed è il file
214
+ * che il titolo nomina. L'azione la dice il prompt.
215
+ */
216
+ export function inboxTitle(f) {
217
+ const name = f.basename.replace(/\.md$/i, '').replace(/\./g, '-');
218
+ return `${INBOX_EMOJI} ${name}`;
219
+ }
195
220
  /**
196
221
  * Il testo del file inbox, per il detail fullscreen. `null` = illeggibile —
197
222
  * il detail lo dice e tiene l'azione attiva, come fa quello della task con un
package/dist/spawn.js CHANGED
@@ -205,10 +205,12 @@ export const ACTION_HOTKEYS = Object.fromEntries(DETAIL_ACTIONS.map((a, i) => [a
205
205
  // (⚙️ ⏱️ 🛠️ …) divergono sempre — string-width le conta 2, VTE ne disegna 1, e
206
206
  // il VS16 non sposta nessuno dei due verdetti. Un'emoji aggiunta qui va prima
207
207
  // passata in `sanitize` per vedere se sopravvive.
208
- // Le stesse quattro sono anche l'unica classe di emoji che `_sane_note` di
209
- // `deck-run` lascia passare nel titolo della tab: la whitelist è una lista
210
- // chiusa che nomina questi code point (P2 preflight), quindi cambiare una voce
211
- // qui e non là riapre la divergenza fra titolo in lista e titolo in tab.
208
+ // Le stesse quattro stanno anche nella whitelist di `_sane_note` in `deck-run`,
209
+ // che è una lista chiusa di code point (P2 preflight): cambiare una voce qui e
210
+ // non là riapre la divergenza fra titolo in lista e titolo in tab. La whitelist
211
+ // è più larga di questa tabella — porta anche le emoji dei titoli che non
212
+ // nascono da un'azione su task (🧹 drain di un file inbox, `src/inbox.ts`; 📏
213
+ // srotolamento di un path, `src/wrap-scan.ts`).
212
214
  // Le quadre che delimitavano il prefisso sono cadute: l'emoji da sola si stacca
213
215
  // già dallo slug, e con lei sono uscite dalla whitelist di `_sane_note`.
214
216
  const ACTION_EMOJI = {
@@ -399,9 +401,30 @@ export function spawnClaudeEmpty(cwd, model) {
399
401
  * Il modello è passato SEMPRE, anche sul default, per la stessa ragione di
400
402
  * `permissionMode`: lo spawn resta deterministico e leggibile nel process tree
401
403
  * invece di dipendere da un default che può cambiare fra versioni.
404
+ *
405
+ * `sessionId` e `title` viaggiano INSIEME o non viaggiano: il titolo lo mostrano
406
+ * due superfici — la tab Ptyxis (via `--title-note`) e la riga della lista
407
+ * sessioni (via la nota nel sidecar, che è keyed sul sessionId) — e senza un id
408
+ * pinnato la seconda non ha una chiave su cui scrivere. Pinnarlo è quindi ciò
409
+ * che rende il titolo visibile in entrambe, non un binding task: una sessione
410
+ * `--no-task` non ne ha uno, ma un NOME ce l'ha.
411
+ *
412
+ * Il titolo sta in TESTA all'argv, prima del prompt: la riga di stato del deck
413
+ * taglia il comando al mezzo, e il prompt (specialmente quello cablato dello
414
+ * srotolamento) è l'argomento più lungo — in coda al titolo lo spingerebbe
415
+ * fuori vista. Stessa ragione dell'ordine di `resumeArgs`.
402
416
  */
403
- export function spawnBare(cwd, prompt, model) {
404
- return launchDeckRun(['--no-task', '--prompt', prompt, '--model', model], cwd);
417
+ export function bareArgs(prompt, model, sessionId, title) {
418
+ const args = ['--no-task'];
419
+ if (title)
420
+ args.push('--title-note', title);
421
+ if (sessionId)
422
+ args.push('--session-id', sessionId);
423
+ args.push('--prompt', prompt, '--model', model);
424
+ return args;
425
+ }
426
+ export function spawnBare(cwd, prompt, model, sessionId, title) {
427
+ return launchDeckRun(bareArgs(prompt, model, sessionId, title), cwd);
405
428
  }
406
429
  // T39/T32: voce `launch` custom del file config, eseguita con cwd = project root.
407
430
  // Spawn detached come spawnDeck: il deck lancia ma non possiede il processo.
package/dist/wrap-scan.js CHANGED
@@ -102,6 +102,31 @@ export function wrapPrompt(path) {
102
102
  'verifica risultato e fai rapporto. se le modifiche sono tutte chiaramente safe, ' +
103
103
  'puoi committare direttamente');
104
104
  }
105
+ /** Prefisso del titolo di una sessione di srotolamento. Astrale, come le altre
106
+ * emoji che finiscono in un titolo: le BMP `sanitize` (`src/width.ts`) le rende
107
+ * `·`. Va tenuta allineata alla whitelist di `_sane_note` in `deck-run`. */
108
+ const WRAP_EMOJI = '📏';
109
+ /**
110
+ * Il titolo della sessione che srotola un path: `📏 <path a parole>`.
111
+ *
112
+ * Le barre diventano spazi e l'estensione cade PRIMA di `_sane_note`, il cui
113
+ * alfabeto non ha né `/` né `.`: lasciandoceli cadere le parole si salderebbero
114
+ * (`runtimereferencedoc-systemmd`), e il titolo della tab divergerebbe da quello
115
+ * che la lista del deck legge dal sidecar.
116
+ *
117
+ * Il path di default (`.`, la project root intera) non ha parole da mostrare e
118
+ * diventa una frase: un titolo `📏 ` sarebbe il solo prefisso.
119
+ */
120
+ export function wrapTitle(path) {
121
+ const target = path.trim() || WRAP_DEFAULT_PATH;
122
+ if (target === WRAP_DEFAULT_PATH)
123
+ return `${WRAP_EMOJI} tutto il progetto`;
124
+ const words = target
125
+ .replace(/\.md$/i, '')
126
+ .replace(/[./]+/g, ' ')
127
+ .trim();
128
+ return `${WRAP_EMOJI} ${words}`;
129
+ }
105
130
  /**
106
131
  * Cartella della cache, una per utente e con `mode 0700` — stessa ragione del
107
132
  * project status: `/tmp` è condivisa, e un file a nome prevedibile può essere
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.66.1",
3
+ "version": "0.67.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": {
package/scripts/deck-run CHANGED
@@ -269,16 +269,20 @@ WORKDIR="${LOOM_DECK_WORKDIR:-$PWD}"
269
269
  # titolo finisce dentro apici singoli in `bash -lc "claude --name '…'"`, e la
270
270
  # nota è testo digitato a mano — enumerare i caratteri pericolosi significa
271
271
  # sbagliarne uno, enumerare quelli ammessi no. Restano lettere, cifre, accentate
272
- # italiane, spazio, trattino, underscore e quattro emoji (sotto); tutto il resto
272
+ # italiane, spazio, trattino, underscore e sei emoji (sotto); tutto il resto
273
273
  # (apici, backslash, `$`, backtick, `;`, quadre, ogni altra emoji…) cade.
274
274
  #
275
- # T156 — le quattro emoji entrano per il PREFISSO del titolo di fallback che il
276
- # deck genera quando il campo nota è vuoto (`🚀 slug`, `ACTION_EMOJI` in
275
+ # T156 — le emoji entrano per il PREFISSO del titolo di fallback che il deck
276
+ # genera quando il campo nota è vuoto (`🚀 slug`, `ACTION_EMOJI` in
277
277
  # `src/spawn.ts`). Senza di loro il prefisso spariva qui e restava nella lista
278
278
  # del deck, che riceve la nota grezza dal sidecar `session-tasks.jsonl`: la
279
279
  # stessa conversazione portava due titoli diversi, senza errore. Le quadre che
280
280
  # delimitavano il prefisso sono cadute col prefisso stesso: l'emoji da sola si
281
281
  # stacca già dallo slug.
282
+ # Quattro nominano un'azione su una task (📐🚀📊🏁); le altre due nominano
283
+ # l'oggetto di una sessione che una task non ce l'ha — 🧹 il file inbox che si
284
+ # drena (`inboxTitle`, `src/inbox.ts`), 📏 il path che si srotola (`wrapTitle`,
285
+ # `src/wrap-scan.ts`).
282
286
  # La whitelist le nomina una per una e NON apre un intervallo di code point. Due
283
287
  # ragioni: un'emoji arriva nella tab solo se il deck la mostra identica in lista,
284
288
  # cioè se `sanitize` (`src/width.ts`) la lascia passare invece di renderla `·`, e
@@ -305,7 +309,7 @@ _sane_note() { # <raw> → nota ridotta, spazi collassati, cap 60 char
305
309
  # `tr` non ne ha bisogno: squeeza uno spazio ASCII, e 0x20 non compare mai
306
310
  # come byte di continuazione di una sequenza UTF-8.
307
311
  s="$(printf '%s' "$1" \
308
- | LC_ALL=C.UTF-8 sed 's/[^A-Za-z0-9 _àèéìòùÀÈÉÌÒÙ📐🚀📊🏁-]//g' \
312
+ | LC_ALL=C.UTF-8 sed 's/[^A-Za-z0-9 _àèéìòùÀÈÉÌÒÙ📐🚀📊🏁🧹📏-]//g' \
309
313
  | tr -s ' ')"
310
314
  s="${s# }"; s="${s% }"
311
315
  printf '%s' "${s:0:60}"