@lamemind/loom-deck 0.74.0 → 0.75.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 +41 -8
- package/dist/cli.js +3 -1
- package/dist/focus-session.js +139 -0
- package/dist/input.js +4 -1
- package/package.json +1 -1
- package/dist/overlays/inbox.js +0 -90
- package/dist/ui/inbox-screen.js +0 -21
package/dist/actions.js
CHANGED
|
@@ -31,6 +31,7 @@ import { useTaskOps } from './task-ops.js';
|
|
|
31
31
|
import { inboxPrompt, inboxWords } from './inbox.js';
|
|
32
32
|
import { docTargetKind, docWords } from './doc-tree.js';
|
|
33
33
|
import { wrapWords } from './wrap-scan.js';
|
|
34
|
+
import { enterSession, focusSession } from './focus-session.js';
|
|
34
35
|
/**
|
|
35
36
|
* T181 — l'attesa massima di un passo del restore prima di passare al
|
|
36
37
|
* successivo. Un `deck-run` esce appena `ptyxis --tab` ha consegnato la tab
|
|
@@ -196,13 +197,21 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, overr
|
|
|
196
197
|
const d = spawnDefaults(kind, taskHoles(task.id));
|
|
197
198
|
spawnForTask(task.id, d.model, d.title ?? '', d.prompt ?? '');
|
|
198
199
|
}
|
|
199
|
-
// T49 — resume di una conversazione in una nuova tab. Unico punto
|
|
200
|
-
// il `⏎` della lista sessioni e quello
|
|
201
|
-
//
|
|
200
|
+
// T49 — resume di una conversazione in una nuova tab. Unico punto dello spawn,
|
|
201
|
+
// con due chiamanti: `openSession` (il `⏎` della lista sessioni e quello
|
|
202
|
+
// sulla riga-sessione della ricerca) quando la conversazione NON è viva, e il
|
|
203
|
+
// restore di T181 (`restoreSessions`).
|
|
204
|
+
// T184 — riprende SEMPRE, e non chiede se la conversazione è viva: quella
|
|
205
|
+
// domanda è del chiamante, e i due chiamanti la pongono in modo diverso. Il
|
|
206
|
+
// `⏎` la pone a ogni pressione (`openSession`); il restore l'ha già posta
|
|
207
|
+
// per l'insieme intero, che `restoreSet` svuota appena una conversazione è
|
|
208
|
+
// viva. Il ramo di focus resta fuori da qui anche per il contratto di
|
|
209
|
+
// ritorno: il restore scandisce la serie sull'uscita del `deck-run` che
|
|
210
|
+
// questa funzione restituisce, e un focus non ha nessun figlio da aspettare.
|
|
202
211
|
// T148 — il modello lo decide il CHIAMANTE, non questa funzione: la lista
|
|
203
212
|
// principale passa il selettore della preview (`model.resumeModel`), la
|
|
204
|
-
// ricerca
|
|
205
|
-
// scelta (D1 preflight). I
|
|
213
|
+
// ricerca e il restore il modello ereditato dalla sessione senza superficie
|
|
214
|
+
// di scelta (D1 preflight). I chiamanti restano la stessa azione perché è
|
|
206
215
|
// lo spawn a essere identico, non la sorgente del valore.
|
|
207
216
|
function resumeSession(sessionId, modelKind) {
|
|
208
217
|
const bound = model.bindings.get(sessionId) ?? null;
|
|
@@ -211,14 +220,37 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, overr
|
|
|
211
220
|
noteSpawn(spawned);
|
|
212
221
|
return spawned;
|
|
213
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* T184 — `⏎` su una conversazione, dalla lista sessioni e dalla riga-sessione
|
|
225
|
+
* della ricerca: la stessa azione dai due posti, come chiede T49.
|
|
226
|
+
*
|
|
227
|
+
* Viva → compass porta davanti la sua tab, e nessun `deck-run` parte: un
|
|
228
|
+
* secondo resume aprirebbe un secondo processo `claude` sullo stesso
|
|
229
|
+
* transcript. Non viva → `resumeSession`, come prima di T184. La scelta e il
|
|
230
|
+
* canale vivono in `focus-session.ts`; qui si cablano coi valori del deck.
|
|
231
|
+
*
|
|
232
|
+
* Il nome della nota viene dal registro (`LiveSession.name`, il titolo della
|
|
233
|
+
* tab); senza nome, il prefisso del `sessionId` come nelle altre note.
|
|
234
|
+
*/
|
|
235
|
+
function openSession(sessionId, modelKind) {
|
|
236
|
+
enterSession(sessionId, {
|
|
237
|
+
isLive: (id) => model.live.has(id),
|
|
238
|
+
nameOf: (id) => model.live.get(id)?.name || id.slice(0, 8),
|
|
239
|
+
focus: focusSession,
|
|
240
|
+
resume: (id) => resumeSession(id, modelKind),
|
|
241
|
+
note: setNote,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
214
244
|
/**
|
|
215
245
|
* T181 — `^T` e il bottone in testata: riapre le conversazioni dell'ultimo
|
|
216
246
|
* insieme vivo, una tab per conversazione, ognuna col proprio binding di
|
|
217
247
|
* task, la propria nota e il modello con cui girava.
|
|
218
248
|
*
|
|
219
|
-
* Passa da `resumeSession
|
|
220
|
-
*
|
|
221
|
-
*
|
|
249
|
+
* Passa da `resumeSession` diretto, non da `openSession` come il `⏎` (T184):
|
|
250
|
+
* l'insieme è vuoto appena una conversazione è viva, quindi qui non c'è
|
|
251
|
+
* nessuna viva da portare davanti. Come la ricerca non ha una superficie di
|
|
252
|
+
* scelta del modello: lo eredita dalla conversazione
|
|
253
|
+
* (`modelAlias(session.model) ?? MODEL_DEFAULT`, T148/D1).
|
|
222
254
|
*
|
|
223
255
|
* IN SERIE, e non tutte insieme (P7 preflight): la tab successiva parte
|
|
224
256
|
* quando il `deck-run` della precedente è uscito, così le tab si aprono
|
|
@@ -663,6 +695,7 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, overr
|
|
|
663
695
|
spawnForTask,
|
|
664
696
|
spawnTaskSession,
|
|
665
697
|
resumeSession,
|
|
698
|
+
openSession,
|
|
666
699
|
restoreSessions,
|
|
667
700
|
cycleResumeModel,
|
|
668
701
|
cycleBareModel,
|
package/dist/cli.js
CHANGED
|
@@ -111,7 +111,9 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
111
111
|
setNote,
|
|
112
112
|
// T148/D1 — nessun selettore nella ricerca: il modello è quello ereditato
|
|
113
113
|
// dalla conversazione, senza superficie di scelta.
|
|
114
|
-
|
|
114
|
+
// T184 — `openSession` come il `⏎` della lista: una viva va davanti, solo
|
|
115
|
+
// una non viva si riprende.
|
|
116
|
+
onResume: (row) => actions.openSession(row.session.sessionId, modelAlias(row.session.model) ?? MODEL_DEFAULT),
|
|
115
117
|
});
|
|
116
118
|
// T121 — il project status. Non è solo un overlay come i tre sopra: il suo
|
|
117
119
|
// stato si legge in TESTATA a schermata chiusa, e il viewer è una delle sue
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { spawnOut } from './spawn.js';
|
|
2
|
+
// Il binario del canale. L'override esiste per il collaudo del ramo «canale
|
|
3
|
+
// assente», stesso schema di `LOOM_DECK_NOTIFY_CMD` (`spawn.ts`): su una
|
|
4
|
+
// macchina dove `gdbus` c'è e compass è sul bus, quel ramo non è altrimenti
|
|
5
|
+
// raggiungibile.
|
|
6
|
+
export const FOCUS_CMD = process.env.LOOM_DECK_FOCUS_CMD ?? 'gdbus';
|
|
7
|
+
/**
|
|
8
|
+
* Il timeout che `gdbus` applica alla risposta, in secondi.
|
|
9
|
+
*
|
|
10
|
+
* compass risponde DOPO il salto: l'helper AT-SPI e la ricerca della finestra
|
|
11
|
+
* costano qualche decimo di secondo ciascuno, quindi la soglia sta un ordine di
|
|
12
|
+
* grandezza sopra il caso normale e scatta solo su un compass appeso. Un compass
|
|
13
|
+
* assente non la aspetta: il bus risponde subito `ServiceUnknown`.
|
|
14
|
+
*/
|
|
15
|
+
export const FOCUS_TIMEOUT_S = 5;
|
|
16
|
+
/**
|
|
17
|
+
* La rete sotto il timeout di `gdbus`: Node uccide il figlio se nemmeno quello
|
|
18
|
+
* esce, per esempio con un bus di sessione che non risponde alla connessione.
|
|
19
|
+
* Senza, un `⏎` resterebbe senza nota per sempre.
|
|
20
|
+
*/
|
|
21
|
+
const FOCUS_KILL_MS = (FOCUS_TIMEOUT_S + 1) * 1000;
|
|
22
|
+
/** Gli esiti che portano alla conversazione: la tab era già davanti
|
|
23
|
+
* (`esatta`) o il salto l'ha portata davanti (`tab`). Ogni altra parola del
|
|
24
|
+
* vocabolario di compass dice perché non ci si è arrivati. */
|
|
25
|
+
const REACHED = new Set(['esatta', 'tab']);
|
|
26
|
+
/** L'argv di `gdbus` per chiedere a compass il focus di `sessionId`. Il
|
|
27
|
+
* `sessionId` va nudo: `gdbus` legge il tipo `s` dall'introspezione del metodo
|
|
28
|
+
* e un UUID non è testo GVariant valido, quindi lo prende come stringa. */
|
|
29
|
+
export function focusSessionArgs(sessionId) {
|
|
30
|
+
return [
|
|
31
|
+
'call',
|
|
32
|
+
'--session',
|
|
33
|
+
'--dest',
|
|
34
|
+
'org.lamemind.Compass',
|
|
35
|
+
'--object-path',
|
|
36
|
+
'/org/lamemind/Compass',
|
|
37
|
+
'--method',
|
|
38
|
+
'org.lamemind.Compass.FocusSession',
|
|
39
|
+
'--timeout',
|
|
40
|
+
String(FOCUS_TIMEOUT_S),
|
|
41
|
+
sessionId,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* L'esito dentro la risposta testuale di `gdbus`, o `null` se la risposta non
|
|
46
|
+
* ha la forma attesa.
|
|
47
|
+
*
|
|
48
|
+
* Un metodo con firma `(s)` esce come tupla GVariant di un elemento:
|
|
49
|
+
* `('tab',)`. La parola non si confronta col vocabolario di compass: un esito
|
|
50
|
+
* che una versione futura aggiungesse deve arrivare nella nota così com'è,
|
|
51
|
+
* invece di confondersi con un canale rotto.
|
|
52
|
+
*/
|
|
53
|
+
export function parseFocusReply(stdout) {
|
|
54
|
+
const m = /^\('([^']*)',\)$/.exec(stdout.trim());
|
|
55
|
+
return m ? m[1] : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Chiede a compass il focus di `sessionId` e consegna l'esito a `onResult`,
|
|
59
|
+
* una volta sola.
|
|
60
|
+
*
|
|
61
|
+
* Il listener su `error` è OBBLIGATORIO e non difensivo: senza `gdbus` (su
|
|
62
|
+
* Windows, o su un Linux senza glib) lo spawn fallisce con un `error`
|
|
63
|
+
* asincrono, e un `error` non agganciato su un `ChildProcess` abbatte il deck.
|
|
64
|
+
* Agganciato, è il ramo «canale assente», ed è anche il solo gate di
|
|
65
|
+
* piattaforma che serve: nessun `process.platform` qui.
|
|
66
|
+
*
|
|
67
|
+
* Il codice d'uscita decide, lo stderr no: `gdbus` lo scrive nella lingua del
|
|
68
|
+
* sistema (`Errore: GDBus.Error:…ServiceUnknown`), e ne resta solo l'ultima
|
|
69
|
+
* riga come motivo, per chi legge il risultato in un test.
|
|
70
|
+
*/
|
|
71
|
+
export function focusSession(sessionId, onResult) {
|
|
72
|
+
const child = spawnOut(FOCUS_CMD, focusSessionArgs(sessionId), {
|
|
73
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
74
|
+
timeout: FOCUS_KILL_MS,
|
|
75
|
+
});
|
|
76
|
+
let out = '';
|
|
77
|
+
let err = '';
|
|
78
|
+
child.stdout?.on('data', (c) => {
|
|
79
|
+
out += c.toString();
|
|
80
|
+
});
|
|
81
|
+
child.stderr?.on('data', (c) => {
|
|
82
|
+
err += c.toString();
|
|
83
|
+
});
|
|
84
|
+
// `error` e `close` possono arrivare entrambi per lo stesso spawn fallito:
|
|
85
|
+
// la nota è una sola.
|
|
86
|
+
let done = false;
|
|
87
|
+
const finish = (r) => {
|
|
88
|
+
if (done)
|
|
89
|
+
return;
|
|
90
|
+
done = true;
|
|
91
|
+
onResult(r);
|
|
92
|
+
};
|
|
93
|
+
child.on('error', (e) => finish({ unreachable: e.code ?? e.message }));
|
|
94
|
+
child.on('close', (code, signal) => {
|
|
95
|
+
if (code !== 0) {
|
|
96
|
+
const last = err.trim().split('\n').pop() ?? '';
|
|
97
|
+
finish({ unreachable: signal ? `timeout (${signal})` : last || `exit ${code}` });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const outcome = parseFocusReply(out);
|
|
101
|
+
finish(outcome === null
|
|
102
|
+
? { unreachable: `risposta inattesa: ${out.trim()}` }
|
|
103
|
+
: { reached: REACHED.has(outcome), outcome });
|
|
104
|
+
});
|
|
105
|
+
return child;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* La nota di un `⏎` su una viva, a ogni esito, successo compreso.
|
|
109
|
+
*
|
|
110
|
+
* Sul successo la tab del deck va dietro, quindi la nota non la legge nessuno
|
|
111
|
+
* subito: si legge al ritorno, ed è l'unica traccia di cosa ha fatto il tasto —
|
|
112
|
+
* ogni spawn del deck lascia già la sua riga di stato, e un `⏎` muto sarebbe
|
|
113
|
+
* l'unica azione senza.
|
|
114
|
+
*
|
|
115
|
+
* `sconosciuta` (compass non trova il `sessionId` fra i vivi) cade nella forma
|
|
116
|
+
* di ogni esito fuori dalla tab, senza un resume: vuol dire che la
|
|
117
|
+
* conversazione è finita dopo l'ultimo tick del deck, e il `⏎` successivo, a
|
|
118
|
+
* tick avvenuto, la vede non viva e la riprende.
|
|
119
|
+
*/
|
|
120
|
+
export function focusNote(r, name) {
|
|
121
|
+
if ('unreachable' in r)
|
|
122
|
+
return `⚠ compass non raggiungibile — ${name}`;
|
|
123
|
+
return r.reached ? `→ ${name}` : `⚠ focus ${r.outcome} — ${name}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* La scelta del `⏎`. Una viva non si riprende MAI, qualunque esito torni:
|
|
127
|
+
* né su `assente` (viva, ma senza una tab col suo nome — lanciata fuori da
|
|
128
|
+
* Ptyxis) né con compass irraggiungibile. In entrambi i casi il processo
|
|
129
|
+
* `claude` c'è, e un resume sarebbe il secondo scrittore.
|
|
130
|
+
*/
|
|
131
|
+
export function enterSession(sessionId, deps) {
|
|
132
|
+
if (!deps.isLive(sessionId)) {
|
|
133
|
+
deps.resume(sessionId);
|
|
134
|
+
return 'resume';
|
|
135
|
+
}
|
|
136
|
+
const name = deps.nameOf(sessionId);
|
|
137
|
+
deps.focus(sessionId, (r) => deps.note(focusNote(r, name)));
|
|
138
|
+
return 'focus';
|
|
139
|
+
}
|
package/dist/input.js
CHANGED
|
@@ -523,6 +523,9 @@ export function useDeckInput({ cwd, tasksDir, mode, setMode, setNote, model, act
|
|
|
523
523
|
else {
|
|
524
524
|
// T49 — ⏎ su una sessione = resume in nuova tab. Il binding si rilegge
|
|
525
525
|
// dal sidecar (non dal padre selezionato): vale anche per le spot.
|
|
526
|
+
// T184 — solo se NON è viva: su una viva compass porta davanti la sua
|
|
527
|
+
// tab, e nessun `deck-run` parte (`openSession`, la stessa azione del
|
|
528
|
+
// `⏎` nella ricerca).
|
|
526
529
|
const s = model.selSessionObj;
|
|
527
530
|
if (!s) {
|
|
528
531
|
setNote(model.selSessionId
|
|
@@ -530,7 +533,7 @@ export function useDeckInput({ cwd, tasksDir, mode, setMode, setNote, model, act
|
|
|
530
533
|
: 'nessuna sessione da riprendere');
|
|
531
534
|
}
|
|
532
535
|
else {
|
|
533
|
-
actions.
|
|
536
|
+
actions.openSession(s.sessionId, model.resumeModel);
|
|
534
537
|
}
|
|
535
538
|
}
|
|
536
539
|
}
|
package/package.json
CHANGED
package/dist/overlays/inbox.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// T134 — il DETAIL di un file inbox: sesta schermata sostitutiva.
|
|
2
|
-
//
|
|
3
|
-
// Stessa forma dello `sheet` della task e per la stessa ragione: il file è
|
|
4
|
-
// FOTOGRAFATO all'apertura — nome, natura, testo — e non è una vista sulla
|
|
5
|
-
// selezione corrente. L'overlay copre la lista, quindi l'oggetto dell'azione
|
|
6
|
-
// deve restare quello che si è scelto anche se un tick dello scan spostasse la
|
|
7
|
-
// selezione sotto.
|
|
8
|
-
//
|
|
9
|
-
// Molto più magro dello sheet: nessuna area di compilazione. Lo sheet ha
|
|
10
|
-
// quattro righe perché quattro parametri dello spawn sono una scelta (azione,
|
|
11
|
-
// prompt, modello, titolo); qui non ce n'è nessuna — la skill la decide la
|
|
12
|
-
// natura (D8), il modello è `MODEL_DEFAULT` per tutte e tre (D11 preflight), la sessione
|
|
13
|
-
// nasce nuda (D10 preflight). Restano la lettura e un tasto.
|
|
14
|
-
//
|
|
15
|
-
// Lo spawn NON sta qui: arriva come callback `onDrain`, per lo stesso confine
|
|
16
|
-
// di `useSheetOverlay` e `useSearchOverlay` — un hook di overlay non esce dal
|
|
17
|
-
// deck.
|
|
18
|
-
import { useMemo, useState } from 'react';
|
|
19
|
-
import { inboxDetailCapacity, pageStep } from '../viewport.js';
|
|
20
|
-
import { wrapWithOffsets } from '../width.js';
|
|
21
|
-
import { parseMarkdown } from '../markdown.js';
|
|
22
|
-
import { inboxPrompt } from '../inbox.js';
|
|
23
|
-
export function useInboxOverlay(deps) {
|
|
24
|
-
const { rows, columns, setMode, setNote, onDrain } = deps;
|
|
25
|
-
const [sheet, setSheet] = useState(null);
|
|
26
|
-
const [top, setTop] = useState(0);
|
|
27
|
-
// Cornici da scalare: box esterno (2 bordi + 2 padding) + box testo (2 bordi
|
|
28
|
-
// + 2 padding) = 8, come il detail della task.
|
|
29
|
-
const width = Math.max(20, (columns || 80) - 8);
|
|
30
|
-
// Il markdown si rende PRIMA del wrap: `**foo**` occupa 3 colonne rese e 7
|
|
31
|
-
// grezze, quindi wrappare sui marker manderebbe a capo su un conteggio che il
|
|
32
|
-
// terminale non disegna. Memo separato dal wrap perché il parse dipende solo
|
|
33
|
-
// dal testo: un resize ri-wrappa, non ri-parsa.
|
|
34
|
-
const doc = useMemo(() => (sheet?.text ? parseMarkdown(sheet.text) : null), [sheet]);
|
|
35
|
-
const lines = useMemo(() => (doc ? wrapWithOffsets(doc.text, width) : []), [doc, width]);
|
|
36
|
-
const capacity = inboxDetailCapacity(rows);
|
|
37
|
-
const maxTop = Math.max(0, lines.length - capacity);
|
|
38
|
-
/** Il prompt che partirà: derivato una volta e mostrato in fondo alla
|
|
39
|
-
* schermata, così `⏎` non è mai un salto nel buio. */
|
|
40
|
-
const prompt = sheet ? inboxPrompt(sheet.file) : '';
|
|
41
|
-
function open(next) {
|
|
42
|
-
setSheet(next);
|
|
43
|
-
setTop(0);
|
|
44
|
-
setNote('');
|
|
45
|
-
setMode('inbox');
|
|
46
|
-
}
|
|
47
|
-
function close() {
|
|
48
|
-
setMode('normal');
|
|
49
|
-
setSheet(null);
|
|
50
|
-
}
|
|
51
|
-
function scroll(delta) {
|
|
52
|
-
setTop((t) => Math.max(0, Math.min(maxTop, t + delta)));
|
|
53
|
-
}
|
|
54
|
-
function onKey(input, key) {
|
|
55
|
-
if (key.escape) {
|
|
56
|
-
close();
|
|
57
|
-
}
|
|
58
|
-
else if (key.return) {
|
|
59
|
-
// Si chiude PRIMA di spawnare, come lo sheet della task: la schermata ha
|
|
60
|
-
// finito il suo lavoro, e lasciarla aperta sopra una tab appena nata
|
|
61
|
-
// farebbe credere che ci sia dell'altro da decidere.
|
|
62
|
-
const s = sheet;
|
|
63
|
-
close();
|
|
64
|
-
if (s)
|
|
65
|
-
onDrain(s.file, inboxPrompt(s.file));
|
|
66
|
-
}
|
|
67
|
-
else if (key.upArrow) {
|
|
68
|
-
scroll(-1);
|
|
69
|
-
}
|
|
70
|
-
else if (key.downArrow) {
|
|
71
|
-
scroll(1);
|
|
72
|
-
}
|
|
73
|
-
else if (key.pageUp) {
|
|
74
|
-
scroll(-pageStep(capacity));
|
|
75
|
-
}
|
|
76
|
-
else if (key.pageDown) {
|
|
77
|
-
scroll(pageStep(capacity));
|
|
78
|
-
}
|
|
79
|
-
else if (input === 'g') {
|
|
80
|
-
// `g`/`G` sugli estremi, come reader, detail e project status: anche
|
|
81
|
-
// questa schermata scorre testo, quindi eredita lo stesso alfabeto invece
|
|
82
|
-
// di inventarne un secondo.
|
|
83
|
-
setTop(0);
|
|
84
|
-
}
|
|
85
|
-
else if (input === 'G') {
|
|
86
|
-
setTop(maxTop);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return { sheet, doc, lines, top, capacity, maxTop, prompt, open, onKey, scroll };
|
|
90
|
-
}
|
package/dist/ui/inbox-screen.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// T134 — il detail di un file inbox: testo scorrevole più la riga che dichiara
|
|
3
|
-
// cosa partirà su `⏎`.
|
|
4
|
-
//
|
|
5
|
-
// Riusa `DetailLine` del detail della task invece di ri-renderizzare il
|
|
6
|
-
// markdown per conto proprio: sono lo stesso costrutto — un documento reso in
|
|
7
|
-
// span tipizzati dentro un box — e due rese dello stesso markdown
|
|
8
|
-
// divergerebbero al primo stile aggiunto. L'evidenziazione della ricerca non
|
|
9
|
-
// serve qui (nessuna ricerca dentro l'inbox), quindi le occorrenze arrivano
|
|
10
|
-
// vuote e il secondo taglio di `DetailLine` non gira nemmeno.
|
|
11
|
-
import { Box, Text } from 'ink';
|
|
12
|
-
import { cut } from '../width.js';
|
|
13
|
-
import { DetailLine } from './detail-screen.js';
|
|
14
|
-
import { INBOX_MARK, WARN, fmtDateTime, fmtSize } from '../glyphs.js';
|
|
15
|
-
import { NATURA_SHORT, inboxMark } from '../inbox.js';
|
|
16
|
-
export function InboxScreen({ file, missing, lines, spans, top, total, capacity, prompt, columns, }) {
|
|
17
|
-
const last = Math.min(total, top + capacity);
|
|
18
|
-
const width = Math.max(20, (columns || 80) - 4);
|
|
19
|
-
const mark = inboxMark(file);
|
|
20
|
-
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: NATURA_SHORT[file.natura] }), ' ', cut(file.basename, Math.max(10, width - 8))] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [INBOX_MARK[mark] ? `${INBOX_MARK[mark]} ` : '', mark, file.branch ? ` ⟨${file.branch}⟩` : '', file.cappello ? ` · ${file.cappello}` : '', " \u00B7 ", file.nozioni, " nozioni \u00B7 ", file.aperte, ' ', "aperte \u00B7 ", fmtSize(file.chars), " \u00B7 ", fmtDateTime(file.created * 1000), missing ? '' : ` · righe ${total === 0 ? 0 : top + 1}-${last} di ${total}`] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193/PgUp/PgDn" }), " testo \u00B7 ", _jsx(Text, { color: "yellow", children: "g/G" }), " estremi \u00B7", ' ', _jsx(Text, { color: "yellow", children: "\u23CE" }), " apre la sessione \u00B7 ", _jsx(Text, { color: "yellow", children: "esc" }), " chiude"] }), _jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: missing ? (_jsxs(Text, { color: "yellow", wrap: "truncate-end", children: [WARN, " file non leggibile \u00B7 l'azione resta attiva (la skill risolve il file per nome)"] })) : (lines.map((l, i) => (_jsx(DetailLine, { line: l, spans: spans, occ: [], current: -1 }, top + i)))) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "\u23CE \u203A " }), _jsx(Text, { color: "green", children: cut(prompt, Math.max(10, width - 4)) })] }) })] }));
|
|
21
|
-
}
|