@lamemind/loom-deck 0.38.0 → 0.40.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/README.md +12 -10
- package/dist/cli.js +32 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Regola unica, senza eccezioni — pensata per reggere l'aggiunta di nuove azioni
|
|
|
52
52
|
| minuscola | azione immediata, one-shot | |
|
|
53
53
|
| `CTRL`+lettera | idiomi universali, toggle dentro i modali di testo, varianti di un'azione | `^F` = find, come ovunque · `^K`/`^P`/`^R` = spawn con un altro prompt |
|
|
54
54
|
| `1`…`9` | voce `launch` n-esima del progetto | da `.claude/loom-works.json` |
|
|
55
|
-
| `
|
|
55
|
+
| `esc` | chiude l'overlay aperto | in modalità normale è inerte: **nessun tasto chiude il deck** |
|
|
56
56
|
|
|
57
57
|
`CTRL` è il terzo livello, aggiunto quando è arrivata la ricerca. Serve perché un modale con **campi di testo** mangia ogni lettera nuda: là dentro nessun comando può essere una lettera semplice. `CTRL+X` e `x` nudo condividono lo stesso `input` e si distinguono solo per `key.ctrl`, quindi in modalità normale il ramo `CTRL` è valutato **per primo** e chiude l'intera classe — senza, `CTRL+F` cadrebbe nel ramo `f` e forkerebbe una sessione invece di cercare.
|
|
58
58
|
|
|
@@ -66,7 +66,7 @@ Assegnazioni correnti:
|
|
|
66
66
|
| modale | `F` | filtri |
|
|
67
67
|
| modale | `^F` | **ricerca full-text** nelle conversazioni del progetto |
|
|
68
68
|
| modale | `⏎` | **detail** della task selezionata: task file scrollabile + barra azioni |
|
|
69
|
-
| modale | `CANC` | **elimina** — la task selezionata
|
|
69
|
+
| modale | `CANC` | **elimina** — la task selezionata; su una riga meta della vista `archiviabili`, l'intero insieme |
|
|
70
70
|
| immediata | `^K` | spawna la task selezionata col prompt di **recap** |
|
|
71
71
|
| immediata | `^P` | spawna la task selezionata sul **preflight** |
|
|
72
72
|
| immediata | `^R` | spawna la task selezionata in **esecuzione** |
|
|
@@ -84,8 +84,8 @@ t 💻 · c 🤖 · 1 📝 codium · 2 ☕ idea
|
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
- **tasti** — cosa puoi fare *qui e ora*. Solo le voci attive: quelle contestuali compaiono quando il pane a fuoco le rende possibili e altrimenti spariscono,
|
|
87
|
-
invece di mostrarsi inerti. Fuori navigazione (`↑↓` `←→`)
|
|
88
|
-
|
|
87
|
+
invece di mostrarsi inerti. Fuori navigazione (`↑↓` `←→`) e indicatore `focus:` — la prima è universale in qualunque TUI, il pane a fuoco si vede già dall'evidenziazione.
|
|
88
|
+
Nessuna voce di uscita, perché non esiste un tasto che chiuda il deck.
|
|
89
89
|
- **surface** — *dove* puoi aprire qualcosa: prima le due built-in (`t`/`c`), poi le voci `launch` del progetto. Stanno insieme perché hanno la stessa natura — fire-once, `cwd` = project root, nessuno stato — e differiscono solo per essere universali invece che custom.
|
|
90
90
|
|
|
91
91
|
L'indice da solo è opaco (le `launch` sono custom per-progetto, senza una lettera fissa per app), quindi la riga espone la mappa e non il conteggio. Se non entrano in larghezza, si ferma a voci intere e mostra il contatore di quelle fuori riga — mai un troncamento silenzioso; le celle delle due built-in sono riservate a monte, o le voci sfonderebbero il box di quel tanto. Il cap a `9` è imposto dai tasti-cifra, non dallo schema: un progetto può dichiarare più di 9 voci, quelle oltre la nona sono configurate ma non raggiungibili (e la riga lo dice).
|
|
@@ -98,12 +98,14 @@ Le emoji sono quelle del menu compass. Per il terminale compass usa 🖥️, che
|
|
|
98
98
|
|
|
99
99
|
Il deck **ordina** la potatura, non la esegue: la fa `loom-works:clean-tasks`, invocato da un processo Claude headless. Nessun `git rm` e nessuna riscrittura di `tasks.md` vivono qui — quella sequenza (task file + folder dot-prefixed + riga in `tasks.md`, un commit atomico per task, symlink `current-task.md` rimosso, righe orfane riconciliate) è implementata una volta sola, e averne una seconda darebbe due rimozioni capaci di divergere.
|
|
100
100
|
|
|
101
|
-
Lo **stesso tasto ha due bersagli di taglia diversa**, e
|
|
101
|
+
Lo **stesso tasto ha due bersagli di taglia diversa**, e a discriminarli è la **selezione prima della vista**:
|
|
102
102
|
|
|
103
|
-
|
|
|
104
|
-
|
|
105
|
-
| `archiviabili` | **tutte** le task che la vista mostra |
|
|
106
|
-
| qualunque altra | la
|
|
103
|
+
| | riga meta (`≡ tutte` / `○ spot`) | riga task |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| vista `archiviabili` | **tutte** le task che la vista mostra | quella task |
|
|
106
|
+
| qualunque altra vista | rifiuto, con la nota che dice quale riga meta è | quella task |
|
|
107
|
+
|
|
108
|
+
Con una riga task sotto il caret il tasto tocca **sempre e solo quella task**. Il bulk richiede quindi di essere usciti da ogni riga task, cioè un movimento in più: l'azione di massa non è mai a un tasto di distanza da quella singola, e non la si preme guardando una task evidenziata credendo che il bersaglio sia lei. Il costo è che la regola non si legge dallo schermo — la legenda la dice (`CANC elimina` contro `CANC elimina tutte`), ed è il solo canale che la rende visibile prima della conferma.
|
|
107
109
|
|
|
108
110
|
Da qui la forma della conferma: nomina il bersaglio (quante e quali ID, troncando con `+N`) invece dell'azione, e dice l'**effetto sul disco** — mai «archiviare», che prometterebbe uno spostamento in un archivio che non esiste. I commit restano **locali**: `clean-tasks` non pusha, quindi finché nessuno lo fa un altro worktree continua a vedere le task potate.
|
|
109
111
|
|
|
@@ -283,7 +285,7 @@ La tab porta anche `PTYXIS_PROFILE` forzata al profilo bindato al progetto nel r
|
|
|
283
285
|
|
|
284
286
|
```bash
|
|
285
287
|
npm install
|
|
286
|
-
npm run dev # tsx src/cli.tsx — lista tasks.md reale, ↑↓ naviga · ⏎ detail · ^K/^P/^R spawn ·
|
|
288
|
+
npm run dev # tsx src/cli.tsx — lista tasks.md reale, ↑↓ naviga · ⏎ detail · ^K/^P/^R spawn · ^C chiude
|
|
287
289
|
npm run build # tsc → dist/
|
|
288
290
|
npm test # node:test sul core vista (src/view.ts), senza Ink né terminale
|
|
289
291
|
```
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { render, Box, Text,
|
|
3
|
+
import { render, Box, Text, useInput } from 'ink';
|
|
4
4
|
import { useState, useEffect, useMemo } from 'react';
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { resolveTasksPath, resolveTasksDir, loadTasks, loadTaskFileText, } from './tasks.js';
|
|
@@ -32,7 +32,6 @@ import { useAssignOverlay } from './overlays/assign.js';
|
|
|
32
32
|
import { captures } from './input-modes.js';
|
|
33
33
|
import { VERSION } from './version.js';
|
|
34
34
|
function Deck({ cwd, tasksPath, tasksDir }) {
|
|
35
|
-
const { exit } = useApp();
|
|
36
35
|
const { tasks, loadError } = useTasks(tasksPath);
|
|
37
36
|
// `notes` esce dall'indice come `sessionNotes`: in questo componente `note` è
|
|
38
37
|
// già la riga di STATO in fondo al frame (il feedback di un'azione). Due
|
|
@@ -124,6 +123,19 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
124
123
|
: selectTasks(tasks, taskViewId, { view, archivable }), [taskViewId, viewTasks, tasks, view, archivable]);
|
|
125
124
|
const isSpot = sel === SPOT;
|
|
126
125
|
const isAll = sel === ALL;
|
|
126
|
+
// T112 — quando `CANC` pota in BLOCCO invece della sola task selezionata.
|
|
127
|
+
//
|
|
128
|
+
// La SELEZIONE batte la vista, non il contrario: con una riga task sotto il
|
|
129
|
+
// caret il tasto tocca quella task e basta, in ogni vista. Il bulk vive solo
|
|
130
|
+
// sulle righe meta della vista `archiviabili`, cioè richiede di essere usciti
|
|
131
|
+
// da ogni riga task — così l'azione di massa non è mai a un tasto di distanza
|
|
132
|
+
// da quella singola, e non si preme guardando una task evidenziata credendo
|
|
133
|
+
// che il bersaglio sia lei.
|
|
134
|
+
//
|
|
135
|
+
// Derivato una volta e letto sia dal ramo di apertura sia dalla legenda: due
|
|
136
|
+
// condizioni scritte due volte direbbero due cose diverse sullo stesso tasto
|
|
137
|
+
// alla prima modifica.
|
|
138
|
+
const purgeBulk = taskViewId === 'archivable' && (isAll || isSpot);
|
|
127
139
|
const projectName = cwd.split('/').pop() || cwd;
|
|
128
140
|
// Unica fonte della selezione: si legge SEMPRE dalla vista, mai dall'array
|
|
129
141
|
// grezzo — è l'invariante che tiene allineati dettaglio mostrato e spawn.
|
|
@@ -426,10 +438,11 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
426
438
|
setNote(ok ? `✔ ${task.id} → ${summary} · committato` : `⚠ ${task.id} salvato, commit fallito: ${err}`);
|
|
427
439
|
});
|
|
428
440
|
}
|
|
429
|
-
// T112 — apertura della conferma di eliminazione. Il tasto è uno
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
//
|
|
441
|
+
// T112 — apertura della conferma di eliminazione. Il tasto è uno e il
|
|
442
|
+
// bersaglio ha due taglie: la task selezionata, o l'insieme intero della vista
|
|
443
|
+
// `archiviabili`. A discriminare è `purgeBulk` — la selezione prima della
|
|
444
|
+
// vista. Il modale nomina comunque il bersaglio (quante e quali) e non
|
|
445
|
+
// l'azione: è l'unico punto in cui la differenza fra i due gesti è leggibile.
|
|
433
446
|
//
|
|
434
447
|
// Il bersaglio del bulk si legge da `paneTasks`, cioè dalla stessa fonte che
|
|
435
448
|
// disegna le righe e alimenta il contatore in header (D6): mai il `Set` grezzo
|
|
@@ -445,7 +458,7 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
445
458
|
setNote('CANC → eliminare: seleziona una task (← per il pane)');
|
|
446
459
|
return;
|
|
447
460
|
}
|
|
448
|
-
const bulk =
|
|
461
|
+
const bulk = purgeBulk;
|
|
449
462
|
const ids = bulk ? paneTasks.map((t) => t.id) : [];
|
|
450
463
|
if (!bulk) {
|
|
451
464
|
const task = selectedTaskOr('CANC', 'eliminare');
|
|
@@ -1113,9 +1126,11 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
1113
1126
|
setNote(`${input} → ${entry.label} su ${projectName}`);
|
|
1114
1127
|
}
|
|
1115
1128
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1129
|
+
// Nessun tasto di uscita: il deck non si chiude da dentro. `q` resta libera
|
|
1130
|
+
// per un binding futuro, `esc` in modalità normale è inerte — dentro un
|
|
1131
|
+
// overlay continua a chiuderlo, perché quel ramo esce prima di qui.
|
|
1132
|
+
// Restano le vie di sistema: `^C` (Ink lo intercetta da sé, `exitOnCtrlC`
|
|
1133
|
+
// di default) e la chiusura della tab Ptyxis che ospita il processo.
|
|
1119
1134
|
});
|
|
1120
1135
|
const canSpawn = focus === 'tasks' && selTask !== null;
|
|
1121
1136
|
const canResume = focus === 'sessions' && selSessionObj !== null;
|
|
@@ -1140,16 +1155,18 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
1140
1155
|
// Legenda della modalità normale. Elenca SOLO i tasti che fanno qualcosa qui e
|
|
1141
1156
|
// ora: le voci contestuali compaiono quando il pane a fuoco le rende possibili
|
|
1142
1157
|
// e altrimenti spariscono, invece di annunciarsi inerti con un `—`.
|
|
1143
|
-
// Fuori: navigazione (`↑↓` `←→`)
|
|
1158
|
+
// Fuori: la navigazione (`↑↓` `←→`), universale in qualunque TUI, e
|
|
1144
1159
|
// l'indicatore `focus:` — il pane a fuoco si vede già dall'evidenziazione, e
|
|
1145
1160
|
// ridirlo a parole costava colonne su una riga che tronca in silenzio.
|
|
1161
|
+
// Nessuna voce di uscita: non esiste più un tasto che chiuda il deck.
|
|
1146
1162
|
const keyLegend = sanitize([
|
|
1147
1163
|
...(canSpawn ? ['⏎ detail', '^K/^P/^R spawn'] : canResume ? ['⏎ resume'] : []),
|
|
1148
|
-
// T112 — la voce nomina il BERSAGLIO, che cambia
|
|
1149
|
-
//
|
|
1150
|
-
// che
|
|
1164
|
+
// T112 — la voce nomina il BERSAGLIO, che cambia di taglia senza che
|
|
1165
|
+
// cambi il tasto. Legge `purgeBulk`, la stessa condizione del ramo di
|
|
1166
|
+
// apertura: una legenda che annunciasse «tutte» dove il tasto ne pota una
|
|
1167
|
+
// sola sarebbe peggio di nessuna legenda.
|
|
1151
1168
|
...(focus === 'tasks'
|
|
1152
|
-
? [
|
|
1169
|
+
? [purgeBulk ? 'CANC elimina tutte' : 'CANC elimina']
|
|
1153
1170
|
: []),
|
|
1154
1171
|
...(canResume ? ['f fork'] : []),
|
|
1155
1172
|
...(canPin ? ['p pin', 'N nota', 'A assegna'] : []),
|
package/package.json
CHANGED