@lamemind/loom-deck 0.35.1 → 0.36.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/cli.js +16 -5
- package/dist/overlays/sheet.js +49 -25
- package/dist/spawn.js +16 -6
- package/dist/ui/detail-screen.js +28 -11
- package/dist/viewport.js +13 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -285,17 +285,28 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
285
285
|
// Lo spawn vero, su una task GIÀ risolta. Separato dalla guardia perché il
|
|
286
286
|
// detail passa l'id fotografato all'apertura e non la selezione corrente: la
|
|
287
287
|
// lista lì sotto non è più a schermo, quindi non è più la fonte dell'oggetto.
|
|
288
|
-
|
|
288
|
+
// T111 — `spawnNote` arriva dal campo sempre attivo del detail ed è vuota per
|
|
289
|
+
// ogni altro percorso (`^K`/`^P`/`^R` non passano di lì, quindi non hanno da
|
|
290
|
+
// dove prenderla). Si scrive nel sidecar PRIMA dello spawn, accanto al
|
|
291
|
+
// binding e per la stessa ragione: la conversazione deve risultare figlia
|
|
292
|
+
// della task e portare la propria maniglia appena il suo JSONL compare, o per
|
|
293
|
+
// il tempo di un tick la riga in lista comparirebbe nuda. Due record separati
|
|
294
|
+
// sullo stesso `sessionId` sono la forma normale di un file append-only
|
|
295
|
+
// last-wins, non una scrittura da fondere.
|
|
296
|
+
function spawnForTask(id, kind, model, keyLabel, spawnNote = '') {
|
|
289
297
|
const sid = randomUUID();
|
|
290
298
|
appendTaskBinding(cwd, sid, id);
|
|
291
|
-
|
|
299
|
+
if (spawnNote)
|
|
300
|
+
appendNote(cwd, sid, spawnNote);
|
|
301
|
+
const child = spawnDeck(id, cwd, sid, kind, model, spawnNote);
|
|
292
302
|
child.on('error', () => setNote(`⚠ spawn ${id} fallito (${DECK_RUN})`));
|
|
293
303
|
const what = kind === 'none' ? '' : ` · ${kind}`;
|
|
294
304
|
// Il modello è SEMPRE nominato, anche quando è il default: gli acceleratori
|
|
295
305
|
// della lista non passano dal selettore del detail e usano il default fisso
|
|
296
306
|
// (T108 · via a), quindi senza dirlo l'utente crederebbe di aver ereditato
|
|
297
307
|
// la scelta fatta nell'ultimo detail aperto.
|
|
298
|
-
|
|
308
|
+
const named = spawnNote ? ` · «${cut(spawnNote, 24)}»` : '';
|
|
309
|
+
setNote(`${keyLabel} spawn ${id}${what} · ${model}${named} → tab CC (sid ${sid.slice(0, 8)})`);
|
|
299
310
|
}
|
|
300
311
|
function spawnTaskSession(kind, keyLabel) {
|
|
301
312
|
const task = selectedTaskOr(keyLabel, 'spawnare');
|
|
@@ -490,7 +501,7 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
490
501
|
columns,
|
|
491
502
|
setMode,
|
|
492
503
|
setNote,
|
|
493
|
-
onAction: (id, kind, model, label) => spawnForTask(id, kind, model, label),
|
|
504
|
+
onAction: (id, kind, model, spawnNote, label) => spawnForTask(id, kind, model, label, spawnNote),
|
|
494
505
|
});
|
|
495
506
|
const search = useSearchOverlay({
|
|
496
507
|
sessions,
|
|
@@ -1023,7 +1034,7 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
1023
1034
|
// posizione è lo scroll mosso a mano. Il clamp serve comunque — un resize
|
|
1024
1035
|
// può accorciare il testo sotto uno scroll già dato.
|
|
1025
1036
|
const start = Math.min(sheet.top, sheet.maxTop);
|
|
1026
|
-
return (_jsx(DetailScreen, { id: sheet.sheet.id, title: sheet.sheet.title, missing: sheet.sheet.text === null, lines: sheet.lines.slice(start, start + sheet.capacity), spans: sheet.doc?.spans ?? [], top: start, total: sheet.lines.length, capacity: sheet.capacity, action: sheet.action, model: sheet.model, columns: columns, find: sheet.find, occ: sheet.findRes.occ, occCur: sheet.occCur }));
|
|
1037
|
+
return (_jsx(DetailScreen, { id: sheet.sheet.id, title: sheet.sheet.title, missing: sheet.sheet.text === null, lines: sheet.lines.slice(start, start + sheet.capacity), spans: sheet.doc?.spans ?? [], top: start, total: sheet.lines.length, capacity: sheet.capacity, action: sheet.action, model: sheet.model, spawnNote: sheet.spawnNote, columns: columns, find: sheet.find, occ: sheet.findRes.occ, occCur: sheet.occCur }));
|
|
1027
1038
|
}
|
|
1028
1039
|
// ── T52 · schermate sostitutive ─────────────────────────────────────────
|
|
1029
1040
|
// Ricerca e reader sono gli unici modali che NON stanno in flusso sopra i
|
package/dist/overlays/sheet.js
CHANGED
|
@@ -28,6 +28,13 @@ export function useSheetOverlay(deps) {
|
|
|
28
28
|
// deck: si azzera a ogni apertura come scroll e azione, quindi non esiste una
|
|
29
29
|
// selezione invisibile che cambi il comportamento dei tasti della lista.
|
|
30
30
|
const [model, setModel] = useState(MODEL_DEFAULT);
|
|
31
|
+
// T111 — la nota con cui nascerà la conversazione. Campo SEMPRE ATTIVO: riceve
|
|
32
|
+
// la scrittura appena il detail si apre, senza nessun tasto che lo apra, e per
|
|
33
|
+
// questo si prende l'alfabeto nudo del modo (le cifre del selettore modello,
|
|
34
|
+
// `g`/`G` degli estremi del testo). Si azzera a ogni apertura come scroll,
|
|
35
|
+
// azione e modello: una nota armata che sopravvive alla chiusura sarebbe uno
|
|
36
|
+
// stato invisibile che cambia il titolo dello spawn successivo.
|
|
37
|
+
const [spawnNote, setSpawnNote] = useState('');
|
|
31
38
|
// T91 — ricerca dentro il detail. `open` distingue i due modi in cui la si
|
|
32
39
|
// lascia: `esc` butta via ciò che il modale ha prodotto (`find` a null,
|
|
33
40
|
// evidenziazione via), `⏎` lo congela e restituisce il controllo allo strato
|
|
@@ -77,12 +84,13 @@ export function useSheetOverlay(deps) {
|
|
|
77
84
|
return;
|
|
78
85
|
setTop(topForOffset(lines, o.start, capacity));
|
|
79
86
|
}, [findRes, occCur, lines, capacity]);
|
|
80
|
-
/** Apre il detail su una task, azzerando scroll, azione, modello e ricerca. */
|
|
87
|
+
/** Apre il detail su una task, azzerando scroll, azione, modello, nota e ricerca. */
|
|
81
88
|
function open(next) {
|
|
82
89
|
setSheet(next);
|
|
83
90
|
setTop(0);
|
|
84
91
|
setAction(0);
|
|
85
92
|
setModel(MODEL_DEFAULT);
|
|
93
|
+
setSpawnNote('');
|
|
86
94
|
setFind(null);
|
|
87
95
|
setOccIdx(0);
|
|
88
96
|
setNote('');
|
|
@@ -150,16 +158,32 @@ export function useSheetOverlay(deps) {
|
|
|
150
158
|
// selezione per ognuna. Il modo cattura TUTTO, acceleratori `^K`/`^P`/`^R`
|
|
151
159
|
// compresi: chi è già nel detail ha i bottoni. L'unica deroga è `^F`, che qui
|
|
152
160
|
// apre la ricerca nel testo invece di quella sulle conversazioni.
|
|
161
|
+
//
|
|
162
|
+
// T111 — con il campo nota sempre attivo l'ultimo ramo è la SCRITTURA, quindi
|
|
163
|
+
// ogni funzione che resta viva deve stare su un tasto che un campo di testo
|
|
164
|
+
// non contende: `tab`, frecce, `PgUp`/`PgDn`, CTRL, `⏎`, `esc`.
|
|
153
165
|
function onKey(input, key) {
|
|
154
166
|
if (find?.open) {
|
|
155
167
|
onFindKey(input, key);
|
|
156
168
|
return;
|
|
157
169
|
}
|
|
158
|
-
if (key.ctrl
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
170
|
+
if (key.ctrl) {
|
|
171
|
+
// Ramo CTRL chiuso in testa, e non più il solo `if` su `^F`: sotto c'è la
|
|
172
|
+
// scrittura nel campo nota, e senza questo ramo `^K` ci finirebbe come
|
|
173
|
+
// lettera `k` — la combo non arriva come tasto proprio, `key.ctrl` è
|
|
174
|
+
// l'unico discriminante fra `^X` e `x`.
|
|
175
|
+
//
|
|
176
|
+
// `^F` è la deroga dichiarata (`CTRL_DEROGATIONS.detail`): fuori dal
|
|
177
|
+
// detail è la ricerca conversazioni, qui è la ricerca nel testo. La query
|
|
178
|
+
// sopravvive a una chiusura con `⏎`, quindi riaprire riprende da dov'era.
|
|
179
|
+
// `^U` svuota la nota — non è una deroga ma una combo che il modo gestisce
|
|
180
|
+
// da sé, come il filtro del modale assegnazione. Ogni altro CTRL è inerte.
|
|
181
|
+
if (input === 'f') {
|
|
182
|
+
setFind((f) => (f ? { ...f, open: true } : { q: '', caret: 0, open: true }));
|
|
183
|
+
}
|
|
184
|
+
else if (input === 'u') {
|
|
185
|
+
setSpawnNote('');
|
|
186
|
+
}
|
|
163
187
|
return;
|
|
164
188
|
}
|
|
165
189
|
if (key.escape) {
|
|
@@ -178,24 +202,18 @@ export function useSheetOverlay(deps) {
|
|
|
178
202
|
// competizione sul tasto.
|
|
179
203
|
const act = DETAIL_ACTIONS[action];
|
|
180
204
|
const id = sheet?.id;
|
|
205
|
+
const note = spawnNote.trim();
|
|
181
206
|
close();
|
|
182
207
|
if (id)
|
|
183
|
-
onAction(id, act.kind, model, `⏎ ${act.label}`);
|
|
208
|
+
onAction(id, act.kind, model, note, `⏎ ${act.label}`);
|
|
184
209
|
}
|
|
185
210
|
else if (key.tab) {
|
|
186
|
-
// T108 — `tab` scorre il catalogo dei modelli
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// alfabeto già speso torna disponibile.
|
|
211
|
+
// T108 — `tab` scorre il catalogo dei modelli, e da T111 è il SOLO canale:
|
|
212
|
+
// le cifre `1`-`4` sono passate al campo nota. Libero solo QUI: in vista
|
|
213
|
+
// normale cicla la vista del pane a fuoco. Il detail cattura l'input per
|
|
214
|
+
// intero, quindi è dove un alfabeto già speso torna disponibile.
|
|
190
215
|
setModel((m) => MODELS[(MODELS.indexOf(m) + 1) % MODELS.length]);
|
|
191
216
|
}
|
|
192
|
-
else if (input.length === 1 && input >= '1' && input <= String(MODELS.length)) {
|
|
193
|
-
// Scelta diretta: chi sa già quale vuole non paga lo scorrimento. Il test
|
|
194
|
-
// sulla lunghezza non è pleonastico: `useInput` consegna il CHUNK letto da
|
|
195
|
-
// stdin, quindi un incollato come `12` passerebbe il confronto fra
|
|
196
|
-
// stringhe e indicizzerebbe il catalogo fuori range.
|
|
197
|
-
setModel(MODELS[Number(input) - 1]);
|
|
198
|
-
}
|
|
199
217
|
else if (key.leftArrow || key.rightArrow) {
|
|
200
218
|
// Scorrimento CICLICO come le righe di scelta del modale edit: cinque
|
|
201
219
|
// voci, arrivare in fondo e ripartire costa meno che invertire direzione.
|
|
@@ -214,14 +232,19 @@ export function useSheetOverlay(deps) {
|
|
|
214
232
|
else if (key.pageDown) {
|
|
215
233
|
scroll(capacity);
|
|
216
234
|
}
|
|
217
|
-
else if (
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
235
|
+
else if (key.backspace || key.delete) {
|
|
236
|
+
// Nessun movimento di caret nel campo (D3): `←→` sono già delle azioni e
|
|
237
|
+
// riprenderle costerebbe la barra. Si cancella quindi in coda — e
|
|
238
|
+
// `key.delete` è ANCHE Backspace in Ink, come nel campo di ricerca.
|
|
239
|
+
setSpawnNote((s) => removeAt(s, cpLen(s) - 1));
|
|
222
240
|
}
|
|
223
|
-
else if (input
|
|
224
|
-
|
|
241
|
+
else if (input && !key.meta) {
|
|
242
|
+
// Ultimo ramo: tutto ciò che nessun tasto vivo ha reclamato è testo. Ci
|
|
243
|
+
// cadono le cifre `1`-`4` (prima il selettore modello) e `g`/`G` (prima
|
|
244
|
+
// gli estremi del testo, che nel detail si raggiungono con
|
|
245
|
+
// `PgUp`/`PgDn`); nel reader fullscreen restano, perché lì nessun campo
|
|
246
|
+
// contende le lettere.
|
|
247
|
+
setSpawnNote((s) => s + sanitizeTyped(input));
|
|
225
248
|
}
|
|
226
249
|
}
|
|
227
250
|
return {
|
|
@@ -230,6 +253,7 @@ export function useSheetOverlay(deps) {
|
|
|
230
253
|
top,
|
|
231
254
|
action,
|
|
232
255
|
model,
|
|
256
|
+
spawnNote,
|
|
233
257
|
find,
|
|
234
258
|
lines,
|
|
235
259
|
capacity,
|
package/dist/spawn.js
CHANGED
|
@@ -32,8 +32,9 @@ export function spawnOut(cmd, args, opts) {
|
|
|
32
32
|
fake.unref = () => fake;
|
|
33
33
|
return fake;
|
|
34
34
|
}
|
|
35
|
-
// L'ordine È il
|
|
36
|
-
//
|
|
35
|
+
// L'ordine È il giro di `tab` nel detail, non una preferenza di lettura:
|
|
36
|
+
// cambiarlo sposta le voci sotto le dita di chi le ha imparate. Fino a T111 era
|
|
37
|
+
// anche il binding delle cifre `1`-`4`, passate poi al campo nota.
|
|
37
38
|
export const MODELS = ['fable', 'opus', 'sonnet', 'haiku'];
|
|
38
39
|
// Default del selettore e di ogni percorso di spawn che non passa da lui
|
|
39
40
|
// (`^K`/`^P`/`^R` dalla lista, resume, fork). Duplicato del default di deck-run
|
|
@@ -65,11 +66,20 @@ export const DETAIL_ACTIONS = [
|
|
|
65
66
|
// Il modello, al contrario del kind, ha un default (T108): i percorsi che non
|
|
66
67
|
// passano dal selettore del detail non devono nominarlo per forza, e l'unico
|
|
67
68
|
// valore sensato per loro è quello che il selettore stesso mostra all'apertura.
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
// T111 — `spawnNote` è la nota data alla NASCITA della conversazione, dal campo
|
|
70
|
+
// sempre attivo del detail. Stesso flag `--title-note` del resume (T64): il
|
|
71
|
+
// suffisso `«nota»` viene appeso a `TITLE` dentro deck-run PRIMA che i rami si
|
|
72
|
+
// separino, quindi il flag non appartiene alla ripresa — vale su ogni spawn.
|
|
73
|
+
// Assente quando la nota è vuota, e non passato vuoto: `--title-note ''`
|
|
74
|
+
// produrrebbe un `«»` a vuoto nel titolo.
|
|
75
|
+
export function deckArgs(id, sessionId, kind, model = MODEL_DEFAULT, spawnNote) {
|
|
76
|
+
const args = [id, '--session-id', sessionId, '--prompt-kind', kind, '--model', model];
|
|
77
|
+
if (spawnNote)
|
|
78
|
+
args.push('--title-note', spawnNote);
|
|
79
|
+
return args;
|
|
70
80
|
}
|
|
71
|
-
export function spawnDeck(id, cwd, sessionId, kind, model = MODEL_DEFAULT) {
|
|
72
|
-
const child = spawnOut(DECK_RUN, deckArgs(id, sessionId, kind, model), {
|
|
81
|
+
export function spawnDeck(id, cwd, sessionId, kind, model = MODEL_DEFAULT, spawnNote) {
|
|
82
|
+
const child = spawnOut(DECK_RUN, deckArgs(id, sessionId, kind, model, spawnNote), {
|
|
73
83
|
cwd,
|
|
74
84
|
detached: true,
|
|
75
85
|
stdio: 'ignore',
|
package/dist/ui/detail-screen.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Box, Text } from 'ink';
|
|
|
5
5
|
import { caretWindow, cut, cutParts, sanitize } from '../width.js';
|
|
6
6
|
import { sliceLine } from '../text-search.js';
|
|
7
7
|
import { sliceSpans } from '../markdown.js';
|
|
8
|
+
import { cpLen } from '../layout.js';
|
|
8
9
|
import { DETAIL_ACTIONS, MODELS } from '../spawn.js';
|
|
9
10
|
import { WARN } from '../glyphs.js';
|
|
10
11
|
/** Resa di ogni costrutto markdown (T75 · D4): un solo livello di enfasi per
|
|
@@ -51,14 +52,19 @@ export function DetailLine({ line, spans, occ, current, }) {
|
|
|
51
52
|
return (_jsx(Text, { bold: st?.bold, color: st?.color, children: sliceLine(seg.text, at, occ, current).map((p, j) => p.hit ? (_jsx(Text, { backgroundColor: p.current ? 'cyan' : 'yellow', color: "black", children: p.text }, j)) : (_jsx(Text, { children: p.text }, j))) }, i));
|
|
52
53
|
}) }));
|
|
53
54
|
}
|
|
54
|
-
/** Campo
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* non su una riga
|
|
58
|
-
|
|
55
|
+
/** Campo di testo del detail: finestra ancorata al caret, cursore inverso sulla
|
|
56
|
+
* cella reale. Gemello di `EditTextField` senza la label, che qui sta fuori
|
|
57
|
+
* perché il campo vive in FLUSSO su una riga condivisa con altro (il contatore
|
|
58
|
+
* di occorrenze per la ricerca, il segnaposto per la nota) — non su una riga
|
|
59
|
+
* propria. Lo usano i due campi del detail: la ricerca `^F`, dove il caret si
|
|
60
|
+
* muove, e la nota (T111), dove il caret sta sempre in coda. */
|
|
61
|
+
export function DetailTextField({ value, caret, cols }) {
|
|
59
62
|
const win = caretWindow(value, caret, cols);
|
|
60
63
|
return (_jsxs(_Fragment, { children: [_jsx(Text, { children: sanitize(win.head) }), _jsx(Text, { inverse: true, children: sanitize(win.at) }), _jsx(Text, { children: sanitize(win.tail) })] }));
|
|
61
64
|
}
|
|
65
|
+
/** Segnaposto della riga nota quando il campo è vuoto. Costante e non letterale
|
|
66
|
+
* inline perché la sua larghezza entra nel budget del campo accanto. */
|
|
67
|
+
const NOTE_HINT = ' · nome della conversazione';
|
|
62
68
|
/**
|
|
63
69
|
* Detail della task (T66): il task file scrollabile più la barra azioni.
|
|
64
70
|
*
|
|
@@ -72,7 +78,7 @@ export function DetailFindField({ value, caret, cols }) {
|
|
|
72
78
|
* all'arrivo del mouse (T21 · SGR enable + hit-test) senza migrazione. La
|
|
73
79
|
* navigazione da tastiera ci si sovrappone senza conflitti.
|
|
74
80
|
*/
|
|
75
|
-
export function DetailScreen({ id, title, missing, lines, spans, top, total, capacity, action, model, columns, find, occ, occCur, }) {
|
|
81
|
+
export function DetailScreen({ id, title, missing, lines, spans, top, total, capacity, action, model, spawnNote, columns, find, occ, occCur, }) {
|
|
76
82
|
const last = Math.min(total, top + capacity);
|
|
77
83
|
// Il taglio lo fa il chiamante (invariante ③ di width.ts): la riga bottoni è
|
|
78
84
|
// ASCII, quindi `truncate-end` oggi darebbe il risultato giusto per caso — ma
|
|
@@ -98,14 +104,16 @@ export function DetailScreen({ id, title, missing, lines, spans, top, total, cap
|
|
|
98
104
|
// enfasi diverse su due righe adiacenti (là `inverse`, qui un grassetto
|
|
99
105
|
// colorato) obbligano a guardare da vicino per capire quale voce è scelta —
|
|
100
106
|
// la stessa resa si legge di colpo su entrambe.
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
107
|
+
// T111 — la cifra è USCITA dalla quadra: `1`-`4` sono passate al campo nota e
|
|
108
|
+
// il modello si scorre col solo `tab`. Un'etichetta che nomina un tasto è
|
|
109
|
+
// accoppiata al binding, e tenerla dopo che il binding è morto non è
|
|
110
|
+
// un'informazione parziale — è una resa che dichiara un tasto che non fa più
|
|
111
|
+
// quella cosa, cioè peggio di nessuna indicazione.
|
|
104
112
|
// `priority` sulla voce SELEZIONATA e non sulla prima: qui il troncamento
|
|
105
113
|
// cancellerebbe l'unica informazione che la riga esiste per dare — quale
|
|
106
114
|
// modello sta per essere usato — mentre nella barra azioni la voce attiva è
|
|
107
115
|
// comunque nota dal tasto appena premuto.
|
|
108
|
-
const mSegs = MODELS.map((m
|
|
116
|
+
const mSegs = MODELS.map((m) => `[ ${m} ]`);
|
|
109
117
|
const mParts = [];
|
|
110
118
|
mSegs.forEach((s, i) => {
|
|
111
119
|
if (i > 0)
|
|
@@ -120,5 +128,14 @@ export function DetailScreen({ id, title, missing, lines, spans, top, total, cap
|
|
|
120
128
|
if (dropped(mShown, mSegs) > 0)
|
|
121
129
|
mShown = cutParts(mParts, Math.max(0, mWidth - 6), mIdx * 2);
|
|
122
130
|
const mCut = dropped(mShown, mSegs);
|
|
123
|
-
|
|
131
|
+
// T111 — riga della nota. FISSA (D1): il valore armato si vede sempre, e il
|
|
132
|
+
// suo costo sta in `DETAIL_CHROME` invece che in un secondo `extra`
|
|
133
|
+
// condizionale di `detailCapacity`. Prefisso largo 8 come `modello `, così i
|
|
134
|
+
// due parametri dello spawn si leggono incolonnati.
|
|
135
|
+
// Il campo non ha nessun tasto che lo apra: il cursore è l'unica cosa che lo
|
|
136
|
+
// dichiara attivo, e il segnaposto dice cosa ci si scrive. Le colonne del
|
|
137
|
+
// segnaposto si riservano SOLO quando c'è (cioè a nota vuota): riservarle
|
|
138
|
+
// sempre toglierebbe testo visibile a una nota lunga per un avviso assente.
|
|
139
|
+
const nWidth = Math.max(10, width - 8 - (spawnNote ? 0 : NOTE_HINT.length));
|
|
140
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "loom-deck" }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: id }), " \u00B7 ", cut(title, Math.max(10, width - 34)), missing ? '' : ` · righe ${total === 0 ? 0 : top + 1}-${last} di ${total}`] }), find?.open ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193" }), " occorrenza \u00B7 ", _jsx(Text, { color: "yellow", children: "\u2190\u2192" }), " caret \u00B7", ' ', _jsx(Text, { color: "yellow", children: "^U" }), " svuota \u00B7 ", _jsx(Text, { color: "yellow", children: "\u23CE" }), " tieni \u00B7", ' ', _jsx(Text, { color: "yellow", children: "esc" }), " annulla"] })) : (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193/PgUp/PgDn" }), " testo \u00B7 ", _jsx(Text, { color: "yellow", children: "\u2190\u2192" }), " azione \u00B7", ' ', _jsx(Text, { color: "yellow", children: "tab" }), " modello \u00B7", ' ', _jsx(Text, { color: "yellow", children: "scrivi" }), " nota \u00B7 ", _jsx(Text, { color: "yellow", children: "^U" }), " svuota \u00B7", ' ', _jsx(Text, { color: "yellow", children: "^F" }), " cerca \u00B7 ", _jsx(Text, { color: "yellow", children: "\u23CE" }), " esegui \u00B7", ' ', _jsx(Text, { color: "yellow", children: "esc" }), " chiudi"] })), _jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: missing ? (_jsxs(Text, { color: "yellow", wrap: "truncate-end", children: [WARN, " task file non trovato \u00B7 le azioni restano attive (deck-run risolve la task per id)"] })) : (lines.map((l, i) => (_jsx(DetailLine, { line: l, spans: spans, occ: occ, current: occCur }, top + i)))) }), find?.open ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "cerca " }), _jsx(DetailTextField, { value: find.q, caret: find.caret, cols: Math.max(10, width - 28) }), find.q.length === 0 ? (_jsx(Text, { dimColor: true, children: " \u00B7 digita per cercare" })) : occ.length === 0 ? (_jsx(Text, { color: "yellow", children: " \u00B7 nessuna occorrenza" })) : (_jsxs(Text, { color: "cyan", children: [' ', "\u00B7 ", occCur + 1, "/", occ.length] }))] }) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "modello " }), mShown.map((part, i) => i % 2 === 1 ? (_jsx(Text, { children: part }, i)) : (_jsx(Text, { inverse: i / 2 === mIdx, color: i / 2 === mIdx ? 'green' : 'gray', children: part }, i))), mCut > 0 ? _jsxs(Text, { color: "yellow", children: [" \u00B7 +", mCut] }) : null] }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "nota " }), _jsx(DetailTextField, { value: spawnNote, caret: cpLen(spawnNote), cols: nWidth }), spawnNote ? null : _jsx(Text, { dimColor: true, children: NOTE_HINT })] }), _jsxs(Text, { wrap: "truncate-end", children: [shown.map((part, i) => i % 2 === 1 ? (_jsx(Text, { children: part }, i)) : (_jsx(Text, { inverse: i / 2 === action, color: i / 2 === action ? 'green' : 'gray', children: part }, i))), cutCount > 0 ? _jsxs(Text, { color: "yellow", children: [" \u00B7 +", cutCount] }) : null] })] })] }));
|
|
124
141
|
}
|
package/dist/viewport.js
CHANGED
|
@@ -218,13 +218,21 @@ export function readerCapacity(rows) {
|
|
|
218
218
|
// 2 bordi del box testo
|
|
219
219
|
// 1 marginTop della riga azioni
|
|
220
220
|
// 1 riga modello (T108)
|
|
221
|
+
// 1 riga nota (T111)
|
|
221
222
|
// 1 riga azioni
|
|
222
223
|
//
|
|
223
|
-
// Le ultime
|
|
224
|
-
// barra bottoni
|
|
225
|
-
// ogni riga fissa aggiunta va scalata dalla capienza
|
|
226
|
-
// sfonda `rows` (stessa invariante di
|
|
227
|
-
|
|
224
|
+
// Le ultime quattro sono il motivo per cui il detail non può riusare
|
|
225
|
+
// READER_CHROME: barra bottoni, selettore modello e campo nota sono righe FISSE
|
|
226
|
+
// in più dentro l'overlay, e ogni riga fissa aggiunta va scalata dalla capienza
|
|
227
|
+
// del contenuto o il frame sfonda `rows` (stessa invariante di
|
|
228
|
+
// TASKS_PANE_CHROME).
|
|
229
|
+
//
|
|
230
|
+
// La nota è una riga fissa e non un secondo `extra` di `detailCapacity` (T111 ·
|
|
231
|
+
// D1): due condizionali mutuamente esclusivi obbligherebbero il parametro a dire
|
|
232
|
+
// QUALE è aperto — un solo booleano per entrambi sottostima quando quello aperto
|
|
233
|
+
// è il più alto, e sommarli toglie righe al testo per un campo che non è a
|
|
234
|
+
// schermo.
|
|
235
|
+
const DETAIL_CHROME = 12;
|
|
228
236
|
// T91 — la ricerca dentro il detail: marginTop + riga del campo.
|
|
229
237
|
//
|
|
230
238
|
// `MODAL_HEIGHT` dice che il detail costa 0 ai due pane (li sostituisce), e da
|
package/package.json
CHANGED