@lamemind/loom-deck 0.67.0 → 0.69.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 +26 -10
- package/dist/actions.js +79 -33
- package/dist/cli.js +51 -6
- package/dist/deck-model.js +21 -18
- package/dist/frame.js +4 -0
- package/dist/glyphs.js +5 -5
- package/dist/hooks.js +86 -3
- package/dist/inbox.js +6 -12
- package/dist/input-modes.js +1 -0
- package/dist/input.js +16 -0
- package/dist/overlays/sheet.js +24 -23
- package/dist/overlays/spawn.js +240 -0
- package/dist/overlays/status.js +2 -2
- package/dist/overlays/wrap.js +2 -2
- package/dist/prompt-catalog.js +1 -1
- package/dist/sane-note.js +115 -0
- package/dist/session-meta.js +108 -0
- package/dist/sessions.js +7 -1
- package/dist/spawn-catalog.js +338 -0
- package/dist/spawn-config.js +280 -0
- package/dist/spawn.js +46 -157
- package/dist/task-index.js +51 -2
- package/dist/tasks.js +21 -1
- package/dist/ui/detail-screen.js +1 -1
- package/dist/ui/preview.js +1 -1
- package/dist/ui/screens.js +12 -1
- package/dist/ui/spawn-screen.js +99 -0
- package/dist/viewport.js +35 -0
- package/dist/wrap-scan.js +14 -33
- package/package.json +1 -1
- package/scripts/deck-run +58 -13
- package/scripts/prompt-catalog +17 -3
package/dist/ui/screens.js
CHANGED
|
@@ -36,6 +36,7 @@ import { DetailScreen } from './detail-screen.js';
|
|
|
36
36
|
import { StatusScreen } from './status-screen.js';
|
|
37
37
|
import { InboxScreen } from './inbox-screen.js';
|
|
38
38
|
import { WrapScreen } from './wrap-screen.js';
|
|
39
|
+
import { SpawnScreen } from './spawn-screen.js';
|
|
39
40
|
import { ReaderScreen, SearchScreen } from './search-screen.js';
|
|
40
41
|
/**
|
|
41
42
|
* Il ripiego per un terminale troppo basso: una riga sola al posto della
|
|
@@ -107,7 +108,7 @@ export function HintBar({ mode, purge, keyLegend, indicators, columns, }) {
|
|
|
107
108
|
*/
|
|
108
109
|
export function screenFor(input) {
|
|
109
110
|
const { mode, rows, columns, note, overlays } = input;
|
|
110
|
-
const { assign, sheet, search, status, inbox, wrap } = overlays;
|
|
111
|
+
const { assign, sheet, search, status, inbox, wrap, spawn } = overlays;
|
|
111
112
|
// ── T57 · schermata di assegnazione ─────────────────────────────────────
|
|
112
113
|
// Sostitutiva come ricerca e reader (D3): la lista task non entra in un box
|
|
113
114
|
// sopra i due pane, e prendendo l'intero frame non costa nulla al loro budget.
|
|
@@ -185,6 +186,16 @@ export function screenFor(input) {
|
|
|
185
186
|
const start = Math.min(wrap.top, wrap.maxTop);
|
|
186
187
|
return (_jsx(WrapScreen, { files: wrap.files.slice(start, start + wrap.capacity), count: wrap.count, mixed: wrap.mixed, mtime: wrap.mtime, top: start, capacity: wrap.capacity, path: wrap.path, caret: wrap.caret, columns: columns }));
|
|
187
188
|
}
|
|
189
|
+
// ── T161 · pagina delle azioni di spawn ─────────────────────────────────
|
|
190
|
+
// Ottava schermata sostitutiva: undici righe per quattro colonne, più l'area
|
|
191
|
+
// di compilazione. In un box sopra i due pane al prompt resterebbero due
|
|
192
|
+
// colonne.
|
|
193
|
+
if (mode === 'spawn' && spawn.open) {
|
|
194
|
+
if (isCompact(spawn.capacity)) {
|
|
195
|
+
return _jsx(CompactNotice, { what: "azioni di spawn", esc: "chiude", rows: rows, columns: columns });
|
|
196
|
+
}
|
|
197
|
+
return (_jsx(SpawnScreen, { rows: spawn.table, sel: spawn.sel, top: Math.min(spawn.top, spawn.maxTop), capacity: spawn.capacity, configured: spawn.table.filter((r) => r.touched).length, dirty: spawn.dirty, editing: spawn.editing, editable: spawn.editable, fieldTitle: spawn.fieldTitle, fieldModel: spawn.fieldModel, fieldPrompt: spawn.fieldPrompt, cursor: spawn.cursor, example: spawn.example, columns: columns, note: note }));
|
|
198
|
+
}
|
|
188
199
|
// ── T52 · ricerca e reader ──────────────────────────────────────────────
|
|
189
200
|
// Gli unici modali che NON stanno in flusso sopra i pane: una lista di
|
|
190
201
|
// occorrenze non entra in un box da 4 righe. Prendono l'intero frame, quindi
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// T161 — la pagina delle azioni di spawn: la tabella e la sua area di
|
|
3
|
+
// compilazione.
|
|
4
|
+
import { Box, Text } from 'ink';
|
|
5
|
+
import { cut, pad, sanitize, termWidth } from '../width.js';
|
|
6
|
+
import { ChoiceRow, FieldText, LABEL_W } from './fields.js';
|
|
7
|
+
import { CARET, CARET_OFF } from '../glyphs.js';
|
|
8
|
+
import { interpolate, saneNote } from '../sane-note.js';
|
|
9
|
+
import { MODELS, SPAWN_FIELDS, spawnCell, } from '../spawn-catalog.js';
|
|
10
|
+
/**
|
|
11
|
+
* Il glifo della riga configurata.
|
|
12
|
+
*
|
|
13
|
+
* Non è riusato da nessun'altra superficie del deck: `●`/`◍` dicono la liveness
|
|
14
|
+
* di un processo, `📌`/`🔗`/`○` l'appartenenza di una conversazione, `✎` il
|
|
15
|
+
* titolo — un falso amico costa più di un simbolo nuovo. Concorde alle due
|
|
16
|
+
* contabilità di larghezza (`agrees('◆')`), quindi `sanitize` lo lascia passare
|
|
17
|
+
* invece di renderlo `·`.
|
|
18
|
+
*/
|
|
19
|
+
const TOUCHED = '◆';
|
|
20
|
+
/** Cella del glifo: si riempie di spazi anche quando è vuota, o le righe non
|
|
21
|
+
* configurate sposterebbero a sinistra tutto ciò che segue. */
|
|
22
|
+
const MARK_W = 2;
|
|
23
|
+
/** Colonna modello: il dominio è chiuso, e a decidere la larghezza non è la voce
|
|
24
|
+
* più lunga (`sonnet`, 6) ma l'INTESTAZIONE — `modello` ne vuole 7, e una
|
|
25
|
+
* testata di colonna troncata con l'ellissi si legge come un difetto. */
|
|
26
|
+
const MODEL_W = 7;
|
|
27
|
+
/** Tetto della colonna funzione: la label più lunga del catalogo (`drain di un
|
|
28
|
+
* file inbox`) sta esattamente dentro. Un tetto più alto le celle le
|
|
29
|
+
* toglierebbe al prompt, che è l'unico dei quattro valori senza una forma
|
|
30
|
+
* breve; più basso troncherebbe due label su undici già a piena larghezza. */
|
|
31
|
+
const FUNC_MAX = 22;
|
|
32
|
+
/** Le etichette delle righe dell'area, in italiano come ogni altra etichetta a
|
|
33
|
+
* schermo: la chiave del campo è un nome del codice e finisce nel file di
|
|
34
|
+
* progetto, non sotto gli occhi di chi compila. */
|
|
35
|
+
const FIELD_LABEL = {
|
|
36
|
+
title: 'titolo',
|
|
37
|
+
model: 'modello',
|
|
38
|
+
prompt: 'prompt',
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Le quattro colonne, per sottrazione come ogni lista del deck: le fisse sono
|
|
42
|
+
* note, e ciò che resta si divide fra titolo e prompt.
|
|
43
|
+
*
|
|
44
|
+
* Il prompt prende la fetta maggiore perché è l'unico dei quattro valori che non
|
|
45
|
+
* ha una forma breve: un titolo tagliato resta riconoscibile dal prefisso, un
|
|
46
|
+
* prompt tagliato a dieci colonne dice solo `/loom-works…`.
|
|
47
|
+
*
|
|
48
|
+
* Pavimento 0 su ogni cella: è un tetto, non una preferenza, e alzarlo sopra lo
|
|
49
|
+
* spazio reale fa uscire la riga dal box mangiandone il bordo (invariante ③ di
|
|
50
|
+
* `width.ts`).
|
|
51
|
+
*/
|
|
52
|
+
export function spawnColumns(width, rows) {
|
|
53
|
+
// `- 4` = bordo + padding del box tabella, che sta DENTRO quello esterno.
|
|
54
|
+
const avail = Math.max(0, width - 4);
|
|
55
|
+
const func = Math.min(FUNC_MAX, Math.max(0, ...rows.map((r) => termWidth(r.action.label))));
|
|
56
|
+
// I tre separatori fra le quattro colonne vanno contati insieme alle celle:
|
|
57
|
+
// sommare solo le celle è l'off-by-one che tronca la coda della riga.
|
|
58
|
+
const rest = Math.max(0, avail - CARET_OFF.length - MARK_W - func - MODEL_W - 3);
|
|
59
|
+
// Il titolo ha un TETTO oltre il quale non cresce: i template sono corti
|
|
60
|
+
// (`📊 {slug}`), quindi su un terminale largo ogni colonna in più gli
|
|
61
|
+
// resterebbe vuota mentre al prompt serve.
|
|
62
|
+
const title = Math.min(28, Math.floor(rest * 0.4));
|
|
63
|
+
return { func, title, model: MODEL_W, prompt: Math.max(0, rest - title) };
|
|
64
|
+
}
|
|
65
|
+
/** Una cella della tabella, col suo colore: il valore in chiaro, il segnaposto
|
|
66
|
+
* smorzato, l'override in ciano — l'origine si legge senza una legenda. */
|
|
67
|
+
function Cell({ row, field, value, width, }) {
|
|
68
|
+
const cell = spawnCell(row.action, field, value, row.raw.overridden.has(field));
|
|
69
|
+
const text = cut(cell.text, width);
|
|
70
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: cell.placeholder, color: cell.origin === 'override' && !cell.placeholder ? 'cyan' : undefined, children: text }), ' '.repeat(Math.max(0, width - termWidth(text)))] }));
|
|
71
|
+
}
|
|
72
|
+
export function SpawnScreen({ rows, sel, top, capacity, configured, dirty, editing, editable, fieldTitle, fieldModel, fieldPrompt, cursor, example, columns, note, }) {
|
|
73
|
+
const width = Math.max(20, (columns || 80) - 4);
|
|
74
|
+
const col = spawnColumns(width, rows);
|
|
75
|
+
const window = rows.slice(top, top + capacity);
|
|
76
|
+
const current = rows[sel];
|
|
77
|
+
/** La riga disegnata `field` è quella in fuoco? Il cursore indicizza le sole
|
|
78
|
+
* celle editabili, la resa le disegna tutte e tre. */
|
|
79
|
+
const focused = (field) => editing && editable[cursor.row] === field;
|
|
80
|
+
const textW = Math.max(10, width - LABEL_W - CARET_OFF.length);
|
|
81
|
+
// L'anteprima si fa sul titolo GIÀ ridotto, come lo farà `deck-run`: mostrare
|
|
82
|
+
// il template interpolato ma non ridotto prometterebbe un titolo che la tab
|
|
83
|
+
// non porta.
|
|
84
|
+
const rendered = saneNote(interpolate(fieldTitle, example) ?? '');
|
|
85
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsxs(Text, { bold: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: "azioni di spawn" }), ' ', _jsxs(Text, { dimColor: true, children: ["\u00B7 ", rows.length, " azioni \u00B7 ", configured, " configurate"] }), dirty ? _jsx(Text, { color: "yellow", children: " \u00B7 modifiche non salvate" }) : null] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193" }), " riga \u00B7 ", _jsx(Text, { color: "yellow", children: "\u23CE" }), ' ', editing ? 'conferma' : 'modifica', " \u00B7 ", _jsx(Text, { color: "yellow", children: "CANC" }), " torna al default \u00B7", ' ', _jsx(Text, { color: "yellow", children: "w" }), " salva \u00B7 ", _jsx(Text, { color: "yellow", children: "esc" }), ' ', editing ? 'annulla' : 'chiude'] }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [CARET_OFF, ' '.repeat(MARK_W), pad('funzione', col.func), " ", pad('titolo', col.title), " ", pad('modello', col.model), ' ', pad('prompt', col.prompt)] }), window.map((r, i) => {
|
|
86
|
+
const at = top + i;
|
|
87
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [at === sel ? CARET : CARET_OFF, _jsx(Text, { color: "cyan", children: pad(r.touched ? TOUCHED : '', MARK_W) }), _jsx(Text, { bold: at === sel, children: pad(cut(r.action.label, col.func), col.func) }), ' ', _jsx(Cell, { row: r, field: "title", value: r.raw.title, width: col.title }), ' ', _jsx(Cell, { row: r, field: "model", value: r.raw.model, width: col.model }), ' ', _jsx(Cell, { row: r, field: "prompt", value: r.raw.prompt, width: col.prompt })] }, r.action.id));
|
|
88
|
+
})] }), editing ? (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [SPAWN_FIELDS.map((field) => {
|
|
89
|
+
const fixed = current.action.fixed[field];
|
|
90
|
+
if (fixed !== undefined) {
|
|
91
|
+
return (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [CARET_OFF, FIELD_LABEL[field].padEnd(LABEL_W), "(", fixed, ")"] }, field));
|
|
92
|
+
}
|
|
93
|
+
if (field === 'model') {
|
|
94
|
+
return (_jsx(ChoiceRow, { label: "modello", values: MODELS, index: Math.max(0, MODELS.indexOf(fieldModel)), focused: focused('model'), width: width }, field));
|
|
95
|
+
}
|
|
96
|
+
const value = field === 'title' ? fieldTitle : fieldPrompt;
|
|
97
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [focused(field) ? CARET : CARET_OFF, _jsx(Text, { dimColor: true, children: FIELD_LABEL[field].padEnd(LABEL_W) }), _jsx(FieldText, { value: value, caret: cursor.caret, focused: focused(field), cols: textW })] }, field));
|
|
98
|
+
}), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [CARET_OFF, 'esempio'.padEnd(LABEL_W), current.action.title === null ? ('nessun titolo per questa azione') : (_jsx(Text, { color: rendered ? 'green' : undefined, children: rendered ? cut(rendered, textW) : 'titolo a vuoto: nessuna nota' }))] })] })) : null, note ? _jsx(Text, { color: "green", wrap: "truncate-end", children: sanitize(note) }) : null] }));
|
|
99
|
+
}
|
package/dist/viewport.js
CHANGED
|
@@ -95,6 +95,10 @@ export const MODAL_HEIGHT = {
|
|
|
95
95
|
// 77 `WRAP` e 147 `misto`, cioè una lista che non entra in nessun box sopra i
|
|
96
96
|
// pane. Altezza da `wrapListCapacity`.
|
|
97
97
|
wrap: 0,
|
|
98
|
+
// T161 — la pagina delle azioni di spawn: ottava sostitutiva. Undici righe di
|
|
99
|
+
// tabella più l'area di compilazione: in un box sopra i pane resterebbero due
|
|
100
|
+
// colonne per il prompt. Altezza da `spawnListCapacity`.
|
|
101
|
+
spawn: 0,
|
|
98
102
|
};
|
|
99
103
|
/**
|
|
100
104
|
* Distribuisce le righe disponibili fra lista task, lista sessioni e preview.
|
|
@@ -335,6 +339,37 @@ const WRAP_CHROME = 12;
|
|
|
335
339
|
export function wrapListCapacity(rows) {
|
|
336
340
|
return Math.max(0, (rows || 24) - SLACK - WRAP_CHROME);
|
|
337
341
|
}
|
|
342
|
+
// T161 — cornice della pagina delle azioni di spawn.
|
|
343
|
+
// 2 bordi del box esterno
|
|
344
|
+
// 1 riga di testata (nome della pagina · quante righe · quante configurate)
|
|
345
|
+
// 1 riga hint
|
|
346
|
+
// 1 marginTop del box tabella
|
|
347
|
+
// 2 bordi del box tabella
|
|
348
|
+
// 1 riga di intestazione delle colonne
|
|
349
|
+
const SPAWN_CHROME = 8;
|
|
350
|
+
// L'area di compilazione: marginTop + le TRE righe (titolo, modello, prompt) +
|
|
351
|
+
// la riga dell'anteprima resa.
|
|
352
|
+
//
|
|
353
|
+
// Fisse e non proporzionali alle celle editabili della riga aperta: su `bare`,
|
|
354
|
+
// che ha il solo modello, le altre due si disegnano comunque per dire perché non
|
|
355
|
+
// si modificano. Un'altezza che dipendesse dalla riga selezionata farebbe
|
|
356
|
+
// saltare la tabella di due righe a ogni movimento del caret, e il budget
|
|
357
|
+
// dovrebbe sapere quale riga è aperta per non sottostimare — lo stesso motivo
|
|
358
|
+
// per cui le cinque righe del detail sono dentro `DETAIL_CHROME` invece che
|
|
359
|
+
// condizionali (T111 · D1).
|
|
360
|
+
const SPAWN_FIELDS_CHROME = 5;
|
|
361
|
+
/**
|
|
362
|
+
* Righe di tabella che entrano nel terminale.
|
|
363
|
+
*
|
|
364
|
+
* Le righe dell'area aperta e la riga di stato si pagano QUI, non altrove: la
|
|
365
|
+
* pagina è una schermata sostitutiva, quindi la nota che le sue azioni scrivono
|
|
366
|
+
* la disegna lei — a differenza della lista, dove la riga di stato sta fuori dai
|
|
367
|
+
* pane e il suo costo è già in `layoutBudget`.
|
|
368
|
+
*/
|
|
369
|
+
export function spawnListCapacity(rows, editing = false, noteLine = false) {
|
|
370
|
+
const extra = (editing ? SPAWN_FIELDS_CHROME : 0) + (noteLine ? 1 : 0);
|
|
371
|
+
return Math.max(0, (rows || 24) - SLACK - SPAWN_CHROME - extra);
|
|
372
|
+
}
|
|
338
373
|
/**
|
|
339
374
|
* Righe scorse da una pressione di PagGiù/PagSu: METÀ della capienza visibile.
|
|
340
375
|
*
|
package/dist/wrap-scan.js
CHANGED
|
@@ -84,48 +84,29 @@ export function mixedCount(files) {
|
|
|
84
84
|
/** Radice di default dell'area di srotolamento: la project root intera (D4). */
|
|
85
85
|
export const WRAP_DEFAULT_PATH = '.';
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* T161 — il buco `{path}` del titolo e del prompt di una sessione di
|
|
88
|
+
* srotolamento: il path reso a parole.
|
|
88
89
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* innocuo — e nessuna delle tre è una preferenza da esporre.
|
|
94
|
-
*
|
|
95
|
-
* Il `<path>` è l'unico buco. Arriva dal campo della lista e finisce dentro
|
|
96
|
-
* `--prompt` come argv singolo, quindi non passa da nessuna shell prima di
|
|
97
|
-
* `deck-run`, che lo quota ad apici singoli: non c'è quoting da fare qui.
|
|
98
|
-
*/
|
|
99
|
-
export function wrapPrompt(path) {
|
|
100
|
-
const target = path.trim() || WRAP_DEFAULT_PATH;
|
|
101
|
-
return (`lancia md-wrap modo apply su ${target} e backup in cartella tmp dedicata. ` +
|
|
102
|
-
'verifica risultato e fai rapporto. se le modifiche sono tutte chiaramente safe, ' +
|
|
103
|
-
'puoi committare direttamente');
|
|
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.
|
|
90
|
+
* Le barre diventano spazi e l'estensione cade QUI, prima di `_sane_note`, il
|
|
91
|
+
* cui alfabeto non ha né `/` né `.`: lasciandoceli cadere le parole si
|
|
92
|
+
* salderebbero (`runtimereferencedoc-systemmd`), e il titolo della tab
|
|
93
|
+
* divergerebbe da quello che la lista del deck legge dal sidecar.
|
|
116
94
|
*
|
|
117
95
|
* Il path di default (`.`, la project root intera) non ha parole da mostrare e
|
|
118
|
-
* diventa una frase:
|
|
96
|
+
* diventa una frase: interpolato in `📏 {path}` darebbe il solo prefisso, cioè
|
|
97
|
+
* un titolo uguale per ogni srotolamento.
|
|
98
|
+
*
|
|
99
|
+
* L'emoji del prefisso non sta più qui: è dentro il template della riga
|
|
100
|
+
* `unwrap` del catalogo delle azioni, dove il progetto può riscriverla.
|
|
119
101
|
*/
|
|
120
|
-
export function
|
|
102
|
+
export function wrapWords(path) {
|
|
121
103
|
const target = path.trim() || WRAP_DEFAULT_PATH;
|
|
122
104
|
if (target === WRAP_DEFAULT_PATH)
|
|
123
|
-
return
|
|
124
|
-
|
|
105
|
+
return 'tutto il progetto';
|
|
106
|
+
return target
|
|
125
107
|
.replace(/\.md$/i, '')
|
|
126
108
|
.replace(/[./]+/g, ' ')
|
|
127
109
|
.trim();
|
|
128
|
-
return `${WRAP_EMOJI} ${words}`;
|
|
129
110
|
}
|
|
130
111
|
/**
|
|
131
112
|
* Cartella della cache, una per utente e con `mode 0700` — stessa ragione del
|
package/package.json
CHANGED
package/scripts/deck-run
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# continuità nuova ........ --resume [--fork]
|
|
14
14
|
# prompt --prompt-kind none|recap|preflight|run|checkpoint | --prompt <testo>
|
|
15
15
|
# modello --model fable|opus|sonnet|haiku
|
|
16
|
+
# Fuori dai quattro, e volutamente non un asse: --new-window, che cambia SOLO il
|
|
17
|
+
# verbo dell'exec finale (tab nella finestra attiva → finestra nuova).
|
|
16
18
|
# Il terzo asse (T56) prima non esisteva: il prompt era una CONSEGUENZA degli
|
|
17
19
|
# altri due (bound ⇒ recap, --no-task ⇒ niente, --resume ⇒ niente), quindi
|
|
18
20
|
# "task-bound SENZA prompt" era inesprimibile e l'unico modo di non avere un
|
|
@@ -56,6 +58,9 @@
|
|
|
56
58
|
# LOOM_DECK_PROFILE_UUID UUID profilo Ptyxis (default: cc-host)
|
|
57
59
|
# LOOM_DECK_WORKDIR dir di lavoro della tab (default: $PWD)
|
|
58
60
|
# LOOM_DECK_ENTER_PROMPT override del prompt iniziale, {TASK}=TaskID (vince sul kind, tranne `none`)
|
|
61
|
+
# T161: inerte sugli spawn che arrivano dal deck TUI —
|
|
62
|
+
# quelli passano SEMPRE `--prompt`, su cui non ha mai
|
|
63
|
+
# vinto. Resta per le invocazioni a mano e per i test.
|
|
59
64
|
# LOOM_DECK_INTAB_CMD override comando in-tab (default: la sessione CC; usato per i test)
|
|
60
65
|
# LOOM_DECK_STATE_PROFILE PTYXIS_PROFILE annunciata a compass (default: bindings/claude
|
|
61
66
|
# del progetto, letto da dconf; settata a vuoto = nessun annuncio)
|
|
@@ -75,6 +80,17 @@ SESSION_ID=""
|
|
|
75
80
|
RESUME_ID=""
|
|
76
81
|
NO_TASK=0
|
|
77
82
|
FORK=0
|
|
83
|
+
# T162 — finestra NUOVA invece di una tab nella finestra attiva.
|
|
84
|
+
#
|
|
85
|
+
# `ptyxis --tab` va sempre nella finestra col focus, e va bene per il deck, che
|
|
86
|
+
# è già una tab della finestra del progetto. Un chiamante che vive FUORI da
|
|
87
|
+
# quella finestra — compass, che riapre una conversazione pinnata di un
|
|
88
|
+
# progetto chiuso — non ha una finestra attiva giusta: senza un verbo diverso
|
|
89
|
+
# la tab finisce nella finestra di un altro progetto, o in una a caso.
|
|
90
|
+
#
|
|
91
|
+
# Non è un quinto asse: non cambia cosa gira nella tab, solo dove la tab nasce.
|
|
92
|
+
# Vale identico nei due rami di spawn (inline e profile).
|
|
93
|
+
NEW_WINDOW=0
|
|
78
94
|
TITLE_NOTE=""
|
|
79
95
|
# Vuoto = flag non passato (≠ un default già risolto): serve a distinguere
|
|
80
96
|
# "kind implicito" da "kind chiesto", perché con --no-task il primo è legittimo
|
|
@@ -154,6 +170,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
154
170
|
FORK=1; shift ;;
|
|
155
171
|
--no-task)
|
|
156
172
|
NO_TASK=1; shift ;;
|
|
173
|
+
--new-window)
|
|
174
|
+
NEW_WINDOW=1; shift ;;
|
|
157
175
|
*)
|
|
158
176
|
if [[ -z "$TASK" ]]; then TASK="$1"; shift
|
|
159
177
|
else echo "argomento inatteso: '$1'" >&2; exit 2; fi ;;
|
|
@@ -187,7 +205,10 @@ USAGE="uso: deck-run <TaskID> [--model <alias>] [--prompt-kind <kind>] [--sessio
|
|
|
187
205
|
valore ignoto → fallback sul default, con avviso su stderr
|
|
188
206
|
|
|
189
207
|
--title-note nota della conversazione, appesa al titolo tab
|
|
190
|
-
(ridotta a lettere/cifre/spazi/-/_, cap 60 char)
|
|
208
|
+
(ridotta a lettere/cifre/spazi/-/_, cap 60 char)
|
|
209
|
+
|
|
210
|
+
--new-window apre una FINESTRA nuova invece di una tab nella finestra
|
|
211
|
+
attiva. Per chi chiama da fuori dalla finestra del progetto"
|
|
191
212
|
|
|
192
213
|
if [[ $NO_TASK -eq 1 && -n "$TASK" ]]; then
|
|
193
214
|
echo "--no-task e <TaskID> sono mutuamente esclusivi" >&2
|
|
@@ -272,17 +293,22 @@ WORKDIR="${LOOM_DECK_WORKDIR:-$PWD}"
|
|
|
272
293
|
# italiane, spazio, trattino, underscore e sei emoji (sotto); tutto il resto
|
|
273
294
|
# (apici, backslash, `$`, backtick, `;`, quadre, ogni altra emoji…) cade.
|
|
274
295
|
#
|
|
275
|
-
# T156 — le emoji entrano per il PREFISSO del titolo
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
# stessa conversazione portava due titoli diversi, senza errore. Le quadre che
|
|
296
|
+
# T156 — le emoji entrano per il PREFISSO del titolo che il deck compone
|
|
297
|
+
# (`🚀 slug`). Senza di loro il prefisso spariva qui e restava nella lista del
|
|
298
|
+
# deck, che riceve la nota grezza dal sidecar `session-tasks.jsonl`: la stessa
|
|
299
|
+
# conversazione portava due titoli diversi, senza errore. Le quadre che
|
|
280
300
|
# delimitavano il prefisso sono cadute col prefisso stesso: l'emoji da sola si
|
|
281
301
|
# stacca già dallo slug.
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
302
|
+
# T161 — le sei stanno tutte nei TEMPLATE di titolo del catalogo delle azioni
|
|
303
|
+
# (`SPAWN_ACTIONS` in `src/spawn-catalog.ts`), che il progetto può riscrivere:
|
|
304
|
+
# quattro nominano un'azione su una task (📐🚀📊🏁), 🧹 il file inbox che si
|
|
305
|
+
# drena, 📏 il path che si srotola.
|
|
306
|
+
# Questa whitelist ha un GEMELLO TypeScript (`saneNote` in `src/sane-note.ts`),
|
|
307
|
+
# che riduce il template al salvataggio perché quello che sta nel file di
|
|
308
|
+
# progetto sia quello che la tab mostrerà. I due lati non si leggono a vicenda e
|
|
309
|
+
# vanno cambiati insieme: il custode è il gate di `test/deck-run.test.ts`, che
|
|
310
|
+
# passa la stessa batteria di input a questa funzione e al gemello e pretende
|
|
311
|
+
# l'uguaglianza.
|
|
286
312
|
# La whitelist le nomina una per una e NON apre un intervallo di code point. Due
|
|
287
313
|
# ragioni: un'emoji arriva nella tab solo se il deck la mostra identica in lista,
|
|
288
314
|
# cioè se `sanitize` (`src/width.ts`) la lascia passare invece di renderla `·`, e
|
|
@@ -386,7 +412,15 @@ MODE_FLAG="--permission-mode ${PERM_MODE} "
|
|
|
386
412
|
|
|
387
413
|
# ── modello (T108 + T152) ────────────────────────────────────────────────────
|
|
388
414
|
# Precedenza: --model > env LOOM_DECK_MODEL > modello del kind nel catalogo >
|
|
389
|
-
# 'fable'.
|
|
415
|
+
# 'fable'.
|
|
416
|
+
#
|
|
417
|
+
# T161 — questa cascata vale per chi invoca deck-run A MANO. Il deck TUI passa
|
|
418
|
+
# SEMPRE `--model` già risolto (default del deck ← override di progetto nel
|
|
419
|
+
# blocco `spawn` di `.claude/loom-works.json`), quindi dal deck si ferma al primo
|
|
420
|
+
# gradino e i tre sotto non intervengono mai. Il blocco di progetto qui non si
|
|
421
|
+
# legge di proposito: `jq` è dipendenza opzionale di questo script, e dargli un
|
|
422
|
+
# JSON da consultare la renderebbe obbligatoria su un percorso che oggi degrada
|
|
423
|
+
# senza. L'ultimo gradino è il gemello bash di `MODEL_DEFAULT` in spawn.ts e
|
|
390
424
|
# va tenuto allineato a mano: i due lati non si leggono a vicenda (il deck deve
|
|
391
425
|
# poter MOSTRARE la selezione iniziale prima che deck-run esista come processo),
|
|
392
426
|
# quindi un cambio qui senza l'altro fa partire dalla lista un modello diverso
|
|
@@ -653,19 +687,30 @@ IN_TAB_CMD="${LOOM_DECK_INTAB_CMD:-$_default_intab}"
|
|
|
653
687
|
# vedono comunque quello che vedevano prima.
|
|
654
688
|
printf 'LOOM_DECK_INTAB %s\n' "$IN_TAB_CMD"
|
|
655
689
|
|
|
690
|
+
# Verbo di apertura (T162): tab nella finestra attiva, o finestra nuova. Una
|
|
691
|
+
# variabile e non due rami duplicati per ogni modalità di spawn — `--new-window`
|
|
692
|
+
# è l'unica differenza fra i due casi.
|
|
693
|
+
if [[ $NEW_WINDOW -eq 1 ]]; then WIN_FLAG="--new-window"; else WIN_FLAG="--tab"; fi
|
|
694
|
+
|
|
656
695
|
case "$MODE" in
|
|
657
696
|
inline)
|
|
658
697
|
# --tab: tab nella window attiva. -- <cmd>: comando inline nella tab.
|
|
659
698
|
# LOOM_TASK viaggia dentro il comando → nessuna scrittura dconf.
|
|
660
|
-
exec ptyxis
|
|
699
|
+
exec ptyxis "$WIN_FLAG" -d "$WORKDIR" -T "${TITLE}" -- bash -lc "$IN_TAB_CMD"
|
|
661
700
|
;;
|
|
662
701
|
profile)
|
|
663
702
|
# --tab-with-profile: riusa il profilo (custom-command FISSO) → per iniettare
|
|
664
703
|
# LOOM_TASK+task va riscritto il custom-command al volo via dconf (stato condiviso).
|
|
704
|
+
# Con --new-window i due flag convivono: la finestra nasce e il profilo
|
|
705
|
+
# decide cosa gira nella sua prima tab.
|
|
665
706
|
dpath="/org/gnome/Ptyxis/Profiles/${PROFILE_UUID}/"
|
|
666
707
|
dconf write "${dpath}use-custom-command" true
|
|
667
708
|
dconf write "${dpath}custom-command" "'bash -lc \"${IN_TAB_CMD//\"/\\\"}\"'"
|
|
668
|
-
|
|
709
|
+
if [[ $NEW_WINDOW -eq 1 ]]; then
|
|
710
|
+
exec ptyxis --new-window --tab-with-profile="$PROFILE_UUID" -d "$WORKDIR" -T "${TITLE}"
|
|
711
|
+
else
|
|
712
|
+
exec ptyxis --tab-with-profile="$PROFILE_UUID" -d "$WORKDIR" -T "${TITLE}"
|
|
713
|
+
fi
|
|
669
714
|
;;
|
|
670
715
|
*)
|
|
671
716
|
echo "LOOM_DECK_SPAWN_MODE ignoto: '$MODE' (usa 'inline' o 'profile')" >&2
|
package/scripts/prompt-catalog
CHANGED
|
@@ -8,9 +8,23 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Perché un file dati e non un `case` dentro deck-run: due lati hanno bisogno
|
|
10
10
|
# dello stesso testo — deck-run per comporre il comando in-tab, il deck TUI per
|
|
11
|
-
# pre-riempire
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# pre-riempire i campi del detail con ciò che sta per essere eseguito. Come
|
|
12
|
+
# codice, il secondo lato dovrebbe riscriverlo, e le due copie divergono alla
|
|
13
|
+
# prima voce aggiunta da una parte sola.
|
|
14
|
+
#
|
|
15
|
+
# T161 — questo file resta la FONTE UNICA dei sette kind, e non è diventato un
|
|
16
|
+
# blocco JSON nel file di progetto per una ragione precisa: `jq` è dipendenza
|
|
17
|
+
# OPZIONALE di deck-run, e un JSON da consultare la renderebbe obbligatoria su
|
|
18
|
+
# un percorso che oggi degrada senza. Le sue righe sono il primo gradino del
|
|
19
|
+
# default di un'azione su task; il secondo è la riga di catalogo in
|
|
20
|
+
# `src/spawn-catalog.ts`, e sopra entrambi sta l'override di progetto nel blocco
|
|
21
|
+
# `spawn` di `.claude/loom-works.json`, che legge il solo deck.
|
|
22
|
+
#
|
|
23
|
+
# Le azioni SENZA kind (la sessione nuda, il drain di un file inbox, lo
|
|
24
|
+
# srotolamento dell'hard-wrap, il project status) non hanno una riga qui e non
|
|
25
|
+
# devono averla: le chiavi di questo file sono i valori di `--prompt-kind`, e
|
|
26
|
+
# quelle quattro non ne passano nessuno — una riga qui sarebbe irraggiungibile
|
|
27
|
+
# da ogni chiamante. I loro default vivono in `src/spawn-catalog.ts`.
|
|
14
28
|
#
|
|
15
29
|
# `none` non compare: nessun prompt non è un template vuoto, è l'assenza della
|
|
16
30
|
# voce. Chi chiede un kind fuori da questo file riceve un prompt vuoto e nessun
|