@lamemind/loom-deck 0.71.0 → 0.71.2
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 +35 -2
- package/dist/deck-model.js +7 -2
- package/dist/doc-tree.js +9 -0
- package/dist/glyphs.js +7 -0
- package/dist/model.js +1 -1
- package/dist/session-list.js +1 -1
- package/dist/sessions.js +36 -7
- package/dist/ui/doc-pane.js +9 -2
- package/dist/ui/panes.js +5 -1
- package/dist/ui/preview.js +2 -2
- package/package.json +1 -1
package/dist/actions.js
CHANGED
|
@@ -25,8 +25,8 @@ import { saveView, viewFilePath } from './view-store.js';
|
|
|
25
25
|
import { onInTabCommand, runLaunch, spawnClaudeEmpty, spawnBare, spawnDeck, spawnDeckFork, spawnDeckResume, spawnTerminal, DECK_RUN, } from './spawn.js';
|
|
26
26
|
import { resolveSpawnId } from './spawn-config.js';
|
|
27
27
|
import { useTaskOps } from './task-ops.js';
|
|
28
|
-
import { inboxWords } from './inbox.js';
|
|
29
|
-
import { docWords } from './doc-tree.js';
|
|
28
|
+
import { inboxPrompt, inboxWords } from './inbox.js';
|
|
29
|
+
import { docTargetKind, docWords } from './doc-tree.js';
|
|
30
30
|
import { wrapWords } from './wrap-scan.js';
|
|
31
31
|
export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, overrides, catalog, setNote, }) {
|
|
32
32
|
// La riga di stato di OGNI spawn di sessione Claude: il comando esatto, come
|
|
@@ -404,6 +404,23 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, overr
|
|
|
404
404
|
* un comando che non si è riusciti a comporre.
|
|
405
405
|
*/
|
|
406
406
|
function rebalanceSpawn(row) {
|
|
407
|
+
// La coda inbox PRIMA dei flag: un file inbox porta sempre il suo flag, e
|
|
408
|
+
// senza questo ramo cadrebbe nel ramo del rebalance con l'aria di essere un
|
|
409
|
+
// bersaglio legittimo. La skill è la stessa che offre il pane di destra —
|
|
410
|
+
// il prompt lo compone `inboxPrompt`, unico posto in cui vive la mappa
|
|
411
|
+
// natura → skill, esattamente come per il `⏎` del pane inbox.
|
|
412
|
+
const kind = docTargetKind(row, model.docsRoot);
|
|
413
|
+
if (kind !== 'doc') {
|
|
414
|
+
const f = kind === 'inbox-file' ? inboxFileAt(row.path) : null;
|
|
415
|
+
if (f)
|
|
416
|
+
return { prompt: inboxPrompt(f), hint: '' };
|
|
417
|
+
return {
|
|
418
|
+
prompt: '',
|
|
419
|
+
hint: kind === 'inbox-dir'
|
|
420
|
+
? `${row.path} è la coda inbox: si drena un file alla volta, non si riorganizza`
|
|
421
|
+
: `${row.path} è un file inbox: la sua natura non è ancora stata misurata, aprilo dal pane di destra`,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
407
424
|
if (row.flags.length === 0) {
|
|
408
425
|
return {
|
|
409
426
|
prompt: '',
|
|
@@ -413,6 +430,13 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, overr
|
|
|
413
430
|
const d = spawnDefaults('rebalance', { target: row.path, words: docWords(row.path) });
|
|
414
431
|
return { prompt: d.prompt ?? '', hint: '' };
|
|
415
432
|
}
|
|
433
|
+
/** Il file della coda inbox che sta dietro una riga dell'albero doc. Le due
|
|
434
|
+
* misure sono due scan distinti dello stesso script: l'albero conosce il
|
|
435
|
+
* path, la natura la sa solo la coda, e il prompt di drain è una funzione
|
|
436
|
+
* della natura. */
|
|
437
|
+
function inboxFileAt(path) {
|
|
438
|
+
return model.inboxAll.find((f) => f.path === path) ?? null;
|
|
439
|
+
}
|
|
416
440
|
/**
|
|
417
441
|
* T160 — apre la sessione che RIORGANIZZA la topologia attorno a un bersaglio.
|
|
418
442
|
*
|
|
@@ -430,6 +454,15 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, overr
|
|
|
430
454
|
function rebalanceDoc(row, prompt) {
|
|
431
455
|
if (!prompt)
|
|
432
456
|
return;
|
|
457
|
+
// Il bersaglio inbox prende la terna della riga `drain`, non quella di
|
|
458
|
+
// `rebalance`: il prompt da solo non basterebbe — modello e titolo della tab
|
|
459
|
+
// verrebbero comunque dalla riga sbagliata, e la conversazione si
|
|
460
|
+
// chiamerebbe «rebalance» mentre esegue un drain.
|
|
461
|
+
const f = inboxFileAt(row.path);
|
|
462
|
+
if (f) {
|
|
463
|
+
drainInbox(f, prompt);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
433
466
|
const sid = randomUUID();
|
|
434
467
|
const d = spawnDefaults('rebalance', { target: row.path, words: docWords(row.path) });
|
|
435
468
|
const title = d.title ?? '';
|
package/dist/deck-model.js
CHANGED
|
@@ -96,7 +96,7 @@ export function sessionColumns(rows, bindings, isAll) {
|
|
|
96
96
|
/**
|
|
97
97
|
* Le conversazioni figlie della selezione: tutte quelle del progetto (`≡
|
|
98
98
|
* tutte`), quelle bound alla task selezionata, oppure (spot) quelle senza
|
|
99
|
-
* binding. `sessions` è già
|
|
99
|
+
* binding. `sessions` è già nell'ordine di `compareSessions` → l'ordine si eredita in tutti e tre i rami.
|
|
100
100
|
*/
|
|
101
101
|
export function childSessionsOf(sessions, bindings, selectedTaskId, isAll) {
|
|
102
102
|
if (isAll)
|
|
@@ -306,7 +306,7 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, bareModel: bareModelDef
|
|
|
306
306
|
// Memoizzato così `sessionRows` resta stabile fra render che non cambiano gli
|
|
307
307
|
// input: l'effect di validità della selezione non rigira a vuoto.
|
|
308
308
|
const childSessions = useMemo(() => childSessionsOf(sessions, bindings, selectedTaskId, isAll), [sessions, bindings, selectedTaskId, isAll]);
|
|
309
|
-
// T133 — lista unica: le figlie del parent, `
|
|
309
|
+
// T133 — lista unica: le figlie del parent, ordine di `compareSessions`, cap su tutte. Le
|
|
310
310
|
// pinnate del progetto escono a parte (`pinnedRows`), per la sola vista `📌`.
|
|
311
311
|
// Core PURO in session-list.ts (testabile senza Ink).
|
|
312
312
|
const assembled = useMemo(() => assembleSessionList(childSessions, sessions, pinned, isAll ? MAX_SESSIONS_ALL : MAX_SESSIONS), [childSessions, sessions, pinned, isAll]);
|
|
@@ -587,12 +587,17 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, bareModel: bareModelDef
|
|
|
587
587
|
// modo del deck
|
|
588
588
|
deckMode,
|
|
589
589
|
// derivazioni del pane inbox
|
|
590
|
+
docsRoot,
|
|
590
591
|
inboxScanned: inbox.scanned,
|
|
591
592
|
inboxOk: inbox.ok,
|
|
592
593
|
inboxViewId,
|
|
593
594
|
inboxCounts,
|
|
594
595
|
inboxStale,
|
|
595
596
|
inboxFiles,
|
|
597
|
+
// La coda INTERA, non filtrata dalla vista: il pane doc la interroga per
|
|
598
|
+
// path e la vista attiva del pane inbox non c'entra niente con quale file
|
|
599
|
+
// l'albero stia mostrando.
|
|
600
|
+
inboxAll: inbox.files,
|
|
596
601
|
selInboxPath,
|
|
597
602
|
selInbox,
|
|
598
603
|
// derivazioni del pane doc
|
package/dist/doc-tree.js
CHANGED
|
@@ -363,6 +363,15 @@ export function countFlagged(data, flags) {
|
|
|
363
363
|
const hit = (f) => f.some((x) => wanted.has(x));
|
|
364
364
|
return data.dirs.filter((d) => hit(d.flags)).length + data.files.filter((f) => hit(f.flags)).length;
|
|
365
365
|
}
|
|
366
|
+
export function inboxDirOf(docsRoot) {
|
|
367
|
+
return `${docsRoot}/inbox`;
|
|
368
|
+
}
|
|
369
|
+
export function docTargetKind(row, docsRoot) {
|
|
370
|
+
const dir = inboxDirOf(docsRoot);
|
|
371
|
+
if (row.kind === 'dir')
|
|
372
|
+
return row.path === dir ? 'inbox-dir' : 'doc';
|
|
373
|
+
return row.path.startsWith(`${dir}/`) || row.flags.includes('INBOX') ? 'inbox-file' : 'doc';
|
|
374
|
+
}
|
|
366
375
|
/**
|
|
367
376
|
* T160 — il buco `{words}` del titolo di una sessione di rebalance: il path reso
|
|
368
377
|
* a parole.
|
package/dist/glyphs.js
CHANGED
|
@@ -228,6 +228,13 @@ export function fmtDateTime(ts) {
|
|
|
228
228
|
const p = (n) => String(n).padStart(2, '0');
|
|
229
229
|
return `${p(d.getDate())}/${p(d.getMonth() + 1)} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
|
230
230
|
}
|
|
231
|
+
/** Come `fmtDateTime`, ma con l'assenza resa esplicita: le date lette dal
|
|
232
|
+
* transcript (`firstPromptTs`, `lastReplyTs`) valgono 0 quando il record non
|
|
233
|
+
* c'è, e 0 formattato darebbe il 01/01/1970 — una data vera al posto di un
|
|
234
|
+
* dato mancante. */
|
|
235
|
+
export function fmtDateTimeOr(ts, absent = '-') {
|
|
236
|
+
return ts > 0 ? fmtDateTime(ts) : absent;
|
|
237
|
+
}
|
|
231
238
|
// T121 — sola ora del giorno, per l'indicatore di project status in testata. Il
|
|
232
239
|
// giorno non ci sta e non servirebbe: un recap del giorno prima è comunque
|
|
233
240
|
// stale, e la cache muore col reboot.
|
package/dist/model.js
CHANGED
|
@@ -6,7 +6,7 @@ export const POLL_MS = 1500;
|
|
|
6
6
|
// — armamento e messaggio scadono insieme, o la riga prometterebbe un'uscita
|
|
7
7
|
// che il tasto non fa più.
|
|
8
8
|
export const QUIT_WINDOW_MS = 5000;
|
|
9
|
-
// Cap del pane sessioni: le più recenti (
|
|
9
|
+
// Cap del pane sessioni: le più recenti (ordine di `compareSessions`), le altre restano nell'indice
|
|
10
10
|
// ma fuori vista. Non-silenzioso → l'header mostra quante sono nascoste.
|
|
11
11
|
export const MAX_SESSIONS = 30;
|
|
12
12
|
// T59 D3 — cap dedicato alla vista "tutte": a 30 su un progetto con ~170
|
package/dist/session-list.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// T133 — Assemblaggio della lista sessioni: UNA lista sola, le figlie del parent
|
|
2
|
-
// selezionato
|
|
2
|
+
// selezionato nell'ordine di `compareSessions`. Il pin è un ATTRIBUTO della riga (glifo, vista
|
|
3
3
|
// dedicata), non una posizione privilegiata.
|
|
4
4
|
// Modulo PURO: nessun import da ink/react, nessun I/O → testabile senza terminale
|
|
5
5
|
// (il pacchetto non ha infrastruttura TUI di test, solo unit sui core puri).
|
package/dist/sessions.js
CHANGED
|
@@ -187,7 +187,9 @@ export function parseTranscript(content, path, mtime, sizeBytes) {
|
|
|
187
187
|
let parentUuid = null;
|
|
188
188
|
let customTitle = '';
|
|
189
189
|
let firstUserText = '';
|
|
190
|
+
let firstUserTs = 0;
|
|
190
191
|
let lastAssistantText = '';
|
|
192
|
+
let lastAssistantTs = 0;
|
|
191
193
|
let model = '';
|
|
192
194
|
let turns = 0;
|
|
193
195
|
const bodies = [];
|
|
@@ -219,6 +221,10 @@ export function parseTranscript(content, path, mtime, sizeBytes) {
|
|
|
219
221
|
}
|
|
220
222
|
if (typeof d.customTitle === 'string' && d.customTitle)
|
|
221
223
|
customTitle = d.customTitle; // last-wins
|
|
224
|
+
// Il `timestamp` del record è ISO UTC; `Date.parse` → epoch ms, NaN se
|
|
225
|
+
// manca o è malformato (i record di stato — `last-prompt`, `mode` — non lo
|
|
226
|
+
// portano affatto). Si legge solo dove serve, cioè accanto ai due testi.
|
|
227
|
+
const recordTs = typeof d.timestamp === 'string' ? Date.parse(d.timestamp) : NaN;
|
|
222
228
|
if (d.type === 'user') {
|
|
223
229
|
// T49: turno = prompt umano. I tool_result viaggiano anch'essi come
|
|
224
230
|
// type:user ma senza blocchi text → extractText '' li esclude.
|
|
@@ -228,8 +234,11 @@ export function parseTranscript(content, path, mtime, sizeBytes) {
|
|
|
228
234
|
const t = extractText(d.message);
|
|
229
235
|
if (t && !isInterrupt(t)) {
|
|
230
236
|
turns++;
|
|
231
|
-
if (!firstUserText)
|
|
237
|
+
if (!firstUserText) {
|
|
232
238
|
firstUserText = t;
|
|
239
|
+
if (!Number.isNaN(recordTs))
|
|
240
|
+
firstUserTs = recordTs;
|
|
241
|
+
}
|
|
233
242
|
}
|
|
234
243
|
}
|
|
235
244
|
else if (d.type === 'assistant') {
|
|
@@ -237,8 +246,11 @@ export function parseTranscript(content, path, mtime, sizeBytes) {
|
|
|
237
246
|
// early-stop — l'ultima riga assistant con testo è quella buona. I
|
|
238
247
|
// record assistant di solo tool_use danno '' e non sovrascrivono.
|
|
239
248
|
const t = extractText(d.message);
|
|
240
|
-
if (t)
|
|
249
|
+
if (t) {
|
|
241
250
|
lastAssistantText = t;
|
|
251
|
+
if (!Number.isNaN(recordTs))
|
|
252
|
+
lastAssistantTs = recordTs;
|
|
253
|
+
}
|
|
242
254
|
// T110 — stesso regime last-wins, ma su un asse suo: un record di solo
|
|
243
255
|
// tool_use porta comunque il modello, e va contato. Nessun costo di I/O:
|
|
244
256
|
// la riga è già parsata per turni, primo prompt e ultima risposta.
|
|
@@ -269,6 +281,8 @@ export function parseTranscript(content, path, mtime, sizeBytes) {
|
|
|
269
281
|
// testo, sanificare a valle sposterebbe l'a-capo già calcolato.
|
|
270
282
|
firstPrompt: sanitize(firstUserText),
|
|
271
283
|
lastReply: sanitize(lastAssistantText),
|
|
284
|
+
firstPromptTs: firstUserTs,
|
|
285
|
+
lastReplyTs: lastAssistantTs,
|
|
272
286
|
// Sanificato come il titolo e le due preview, e per lo stesso motivo: è una
|
|
273
287
|
// stringa di sorgente esterna che finisce NEL FRAME (riga meta del blocco
|
|
274
288
|
// preview). Il confine di sanificazione è per SORGENTE, non per campo — un
|
|
@@ -279,9 +293,24 @@ export function parseTranscript(content, path, mtime, sizeBytes) {
|
|
|
279
293
|
bodies,
|
|
280
294
|
};
|
|
281
295
|
}
|
|
296
|
+
/** L'ordine della lista sessioni: ultima risposta del modello, più recente in
|
|
297
|
+
* cima. Chi non ha ancora una risposta con testo (`lastReplyTs === 0`: la
|
|
298
|
+
* prima risposta è in corso, o non è mai arrivata) sta sopra tutte — è la
|
|
299
|
+
* conversazione che aspetta, non quella che ha finito. Il mtime (`ts`) resta
|
|
300
|
+
* solo come spareggio: si sposta anche aprendo la conversazione senza farci
|
|
301
|
+
* nulla, e come chiave prima metteva in cima una sessione soltanto guardata. */
|
|
302
|
+
export function compareSessions(a, b) {
|
|
303
|
+
const aOpen = a.lastReplyTs === 0;
|
|
304
|
+
const bOpen = b.lastReplyTs === 0;
|
|
305
|
+
if (aOpen !== bOpen)
|
|
306
|
+
return aOpen ? -1 : 1;
|
|
307
|
+
if (a.lastReplyTs !== b.lastReplyTs)
|
|
308
|
+
return b.lastReplyTs - a.lastReplyTs;
|
|
309
|
+
return b.ts - a.ts;
|
|
310
|
+
}
|
|
282
311
|
// Discovery read-only delle sessioni del SOLO progetto corrente (D2 preflight
|
|
283
312
|
// T27): legge la project dir calcolata dal forward-transform, filtra per cwd
|
|
284
|
-
// (difesa contro le collisioni lossy del naming), ordina
|
|
313
|
+
// (difesa contro le collisioni lossy del naming), ordina con `compareSessions`.
|
|
285
314
|
export function discoverProjectSessions(projectRoot) {
|
|
286
315
|
const dir = join(claudeProjectsRoot(), projectDirName(projectRoot));
|
|
287
316
|
let files;
|
|
@@ -322,12 +351,12 @@ export function discoverProjectSessions(projectRoot) {
|
|
|
322
351
|
for (const key of cache.keys())
|
|
323
352
|
if (!seen.has(key))
|
|
324
353
|
cache.delete(key);
|
|
325
|
-
out.sort(
|
|
354
|
+
out.sort(compareSessions);
|
|
326
355
|
return out;
|
|
327
356
|
}
|
|
328
357
|
// Raggruppa per gitBranch (D2: group-by-branch nel progetto corrente). Ordine
|
|
329
|
-
// dei gruppi = sessione
|
|
330
|
-
// l'ordine
|
|
358
|
+
// dei gruppi = la prima sessione di ciascuno, con `compareSessions`; dentro il
|
|
359
|
+
// gruppo resta l'ordine ereditato dall'input.
|
|
331
360
|
export function groupByBranch(sessions) {
|
|
332
361
|
const map = new Map();
|
|
333
362
|
for (const s of sessions) {
|
|
@@ -340,5 +369,5 @@ export function groupByBranch(sessions) {
|
|
|
340
369
|
}
|
|
341
370
|
return [...map.entries()]
|
|
342
371
|
.map(([branch, arr]) => ({ branch, sessions: arr }))
|
|
343
|
-
.sort((a, b) =>
|
|
372
|
+
.sort((a, b) => compareSessions(a.sessions[0], b.sessions[0]));
|
|
344
373
|
}
|
package/dist/ui/doc-pane.js
CHANGED
|
@@ -73,13 +73,20 @@ export function DocPane({ rows, counts, activeView, paneCount, selectedPath, foc
|
|
|
73
73
|
? 'misura della doc fallita: plugin assente o script in errore'
|
|
74
74
|
: docView(activeView).empty, width) })) : (rows.map((r) => {
|
|
75
75
|
const sel = r.path === selectedPath;
|
|
76
|
-
|
|
76
|
+
// Il rientro sta FUORI da `cut`, che normalizza il whitespace e taglia
|
|
77
|
+
// gli estremi (`\s+ → spazio`, poi `trim`): passarglielo insieme al
|
|
78
|
+
// nome lo faceva sparire, e l'albero usciva piatto pur avendo le
|
|
79
|
+
// profondità giuste. Il budget si divide qui — prima le colonne del
|
|
80
|
+
// rientro, il resto al nome — e il rientro si ferma una colonna prima
|
|
81
|
+
// della fine, o su un pane strettissimo mangerebbe il nome intero.
|
|
82
|
+
const indentW = Math.min(r.depth * INDENT_W, Math.max(0, nameW - 1));
|
|
83
|
+
const indent = ' '.repeat(indentW);
|
|
77
84
|
// La barra finale distingue una cartella da un file senza spendere una
|
|
78
85
|
// colonna di marcatore: è la convenzione di `ls -F`, e su una riga che
|
|
79
86
|
// porta già una colonna di flag un glifo in più sarebbe la terza cosa
|
|
80
87
|
// da leggere prima del nome.
|
|
81
88
|
const label = r.kind === 'dir' ? `${r.name}/` : r.name;
|
|
82
|
-
const text =
|
|
89
|
+
const text = indent + cut(label, nameW - indentW);
|
|
83
90
|
return (_jsxs(Text, { inverse: sel && focused, bold: sel && !focused, wrap: "truncate-end", children: [sel ? CARET : CARET_OFF, _jsx(Text, { color: flagColor(r), dimColor: r.flags.length === 0, children: flagCell(r.flags) }), ' ', _jsx(Text, { bold: r.kind === 'dir', dimColor: r.kind === 'file' && r.flags.length === 0, children: text })] }, r.path));
|
|
84
91
|
}))] }));
|
|
85
92
|
}
|
package/dist/ui/panes.js
CHANGED
|
@@ -152,7 +152,11 @@ export function SessionsPane({ parentLabel, isSpot, isAll, bindings, taskW, ageW
|
|
|
152
152
|
// caratteri). Il marker va portato a 2 anche quando è `○`, largo 1:
|
|
153
153
|
// era lui a far slittare a sinistra di una colonna tutta la riga di
|
|
154
154
|
// ogni sessione spot.
|
|
155
|
-
|
|
155
|
+
// L'età è quella dell'ultima risposta, la stessa chiave che ordina la
|
|
156
|
+
// lista (`compareSessions`): un'età dal mtime accanto a un ordine per
|
|
157
|
+
// risposta darebbe una colonna che non cresce scendendo. `-` quando
|
|
158
|
+
// la risposta non c'è ancora — la riga sta in cima per quello.
|
|
159
|
+
const age = s.lastReplyTs > 0 ? relTime(s.lastReplyTs) : '-';
|
|
156
160
|
// Il taglio del titolo è ciò che RESTA, calcolato per sottrazione: le
|
|
157
161
|
// colonne fisse sono note, quindi l'unica cella elastica prende il
|
|
158
162
|
// resto. Pavimento `0` e non un minimo di cortesia — è un tetto, non
|
package/dist/ui/preview.js
CHANGED
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
4
4
|
import { Box, Text } from 'ink';
|
|
5
5
|
import { cut, cutParts, wrapLines } from '../width.js';
|
|
6
6
|
import { previewTextWidth, previewTitleParts } from '../layout.js';
|
|
7
|
-
import { LIVE_BUSY, LIVE_IDLE, META_KEYS, fmtDateTime, fmtSize, modelAlias, } from '../glyphs.js';
|
|
7
|
+
import { LIVE_BUSY, LIVE_IDLE, META_KEYS, fmtDateTime, fmtDateTimeOr, fmtSize, modelAlias, } from '../glyphs.js';
|
|
8
8
|
import { NATURA_SHORT, inboxMark } from '../inbox.js';
|
|
9
9
|
import { flagNames } from '../doc-tree.js';
|
|
10
10
|
import { ChoiceRow } from './fields.js';
|
|
@@ -93,7 +93,7 @@ export function SessionPreview({ s, firstLines, lastLines, columns, origin, note
|
|
|
93
93
|
// di una colonna per emoji (invariante ③ di width.ts) e mangia il bordo del
|
|
94
94
|
// box. Pezzi e ragione stanno in `previewTitleParts`.
|
|
95
95
|
const [sidPart, notePart, titlePart] = cutParts(previewTitleParts(s.sessionId, note, s.title), previewTextWidth(columns));
|
|
96
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: sidPart }), notePart ? _jsx(Text, { color: "yellow", children: notePart }) : null, _jsx(Text, { dimColor: Boolean(note), children: titlePart })] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [fmtSize(s.sizeBytes), " \u00B7 ", s.turns, " turni \u00B7 ",
|
|
96
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: sidPart }), notePart ? _jsx(Text, { color: "yellow", children: notePart }) : null, _jsx(Text, { dimColor: Boolean(note), children: titlePart })] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [fmtSize(s.sizeBytes), " \u00B7 ", s.turns, " turni \u00B7 \u00BB ", fmtDateTimeOr(s.firstPromptTs), " \u00B7 \u00AB ", fmtDateTimeOr(s.lastReplyTs), " \u00B7 ", s.gitBranch || '-', s.model ? ` · ${s.model}` : '', origin ? ` · ⑂ da ${origin.slice(0, 8)}` : '', live ? (_jsx(Text, { color: live.status === 'busy' ? 'yellow' : 'green', children: ` · ${live.status === 'busy' ? LIVE_BUSY : LIVE_IDLE} viva pid ${live.pid} (${live.status})` })) : null] }), _jsx(ChoiceRow, { label: "modello", values: MODELS, index: Math.max(0, MODELS.indexOf(resumeModel)), focused: false, width: previewTextWidth(columns), originIndex: originIndex }), first.map((line, i) => (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [i === 0 ? '» ' : ' ', line] }, `f${i}`))), last.map((line, i) => (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [i === 0 ? '« ' : ' ', line] }, `l${i}`)))] }));
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
99
|
* Righe non-wrappabili del dettaglio (titolo + meta + commit) e loro conteggio.
|
package/package.json
CHANGED