@lamemind/loom-deck 0.51.0 → 0.53.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 +113 -25
- package/dist/mouse.js +84 -0
- package/dist/pane-header.js +62 -0
- package/dist/ui/panes.js +15 -39
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,8 @@ import { rowIndexOfKey, selectedRow, } from './search.js';
|
|
|
8
8
|
import { appendNote, appendPin, appendSessionRecord, appendTaskBinding, } from './task-index.js';
|
|
9
9
|
import { assembleSessionList, firstSelectableId, moveSelection, neighborId, rowIndexOf, selectedSession, unpinLandingId, } from './session-list.js';
|
|
10
10
|
import { cellWidth, launchLegend, LAUNCH_SEP, loadArchivableDays, loadIdentity, loadLaunch, } from './config.js';
|
|
11
|
-
import { anchorFrame, enableMouse, FRAME_TEXT_COL, hitRegion, isWheel, LAUNCH_ROW, rowRegions, takeMouse, WHEEL_LINES, wheelDir, } from './mouse.js';
|
|
11
|
+
import { anchorFrame, enableMouse, FRAME_TEXT_COL, hitRegion, inlineRegions, isWheel, LAUNCH_ROW, listHit, PANE_TEXT_PAD, paneSpans, rowRegions, takeMouse, WHEEL_LINES, wheelDir, } from './mouse.js';
|
|
12
|
+
import { headerItems, sessionHeaderParts, taskHeaderParts } from './pane-header.js';
|
|
12
13
|
import { cycleSessionView, cycleTaskView, selectSessionRows, selectTasks, sessionView, taskView, TASK_VIEWS, } from './pane-views.js';
|
|
13
14
|
import { isCompact, layoutBudget, searchPreviewCapacity, windowRange, } from './viewport.js';
|
|
14
15
|
import { cut, cutMiddle, sanitize, termWidth } from './width.js';
|
|
@@ -713,13 +714,17 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
713
714
|
// 2..N+1 = task visibili) e la riconverte subito in sentinella o id: l'indice
|
|
714
715
|
// non sopravvive a un cambio di filtro, l'id sì.
|
|
715
716
|
function moveTaskSel(delta) {
|
|
716
|
-
|
|
717
|
-
|
|
717
|
+
selectTaskRow(Math.max(0, Math.min(paneTasks.length + META_ROWS - 1, selIndex + delta)));
|
|
718
|
+
}
|
|
719
|
+
/** Selezione per INDICE nella vista, riconvertita subito in sentinella o id.
|
|
720
|
+
* T21 — la chiama anche il click su una riga del pane task. */
|
|
721
|
+
function selectTaskRow(index) {
|
|
722
|
+
if (index === ROW_ALL)
|
|
718
723
|
setSel(ALL);
|
|
719
|
-
else if (
|
|
724
|
+
else if (index === ROW_SPOT)
|
|
720
725
|
setSel(SPOT);
|
|
721
726
|
else
|
|
722
|
-
setSel(paneTasks[
|
|
727
|
+
setSel(paneTasks[index - META_ROWS]?.id ?? SPOT);
|
|
723
728
|
}
|
|
724
729
|
// T100 — `tab` naviga il catalogo viste del pane in focus. Il reset della
|
|
725
730
|
// selezione è la regola letterale «prima riga in alto», senza eccezioni: sul
|
|
@@ -728,18 +733,28 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
728
733
|
// sessioni basta invalidare l'id, e l'effect di validità atterra sulla prima
|
|
729
734
|
// riga selezionabile della vista nuova.
|
|
730
735
|
function cycleView(delta) {
|
|
731
|
-
if (focus === 'tasks')
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
736
|
+
if (focus === 'tasks')
|
|
737
|
+
selectTaskView(cycleTaskView(taskViewId, delta));
|
|
738
|
+
else
|
|
739
|
+
selectSessionView(cycleSessionView(sessionViewId, delta));
|
|
740
|
+
}
|
|
741
|
+
// T21 — per VISTA e non per passo: il click sull'header nomina una voce, e
|
|
742
|
+
// `tab` la raggiunge ciclando. Sulla vista già attiva non si fa niente — un
|
|
743
|
+
// click che azzerasse la selezione alla riga in alto sarebbe un effetto che
|
|
744
|
+
// la tastiera non ha mai avuto, perché `tab` non può restare ferma.
|
|
745
|
+
function selectTaskView(next) {
|
|
746
|
+
if (next === taskViewId)
|
|
747
|
+
return;
|
|
748
|
+
setTaskViewId(next);
|
|
749
|
+
setSel(ALL);
|
|
750
|
+
setNote(`vista task: ${taskView(next).label(taskCounts)}`);
|
|
751
|
+
}
|
|
752
|
+
function selectSessionView(next) {
|
|
753
|
+
if (next === sessionViewId)
|
|
754
|
+
return;
|
|
755
|
+
setSessionViewId(next);
|
|
756
|
+
setSelSessionId(null);
|
|
757
|
+
setNote(`vista sessioni: ${sessionView(next).label(sessionCounts, parentLabel)}`);
|
|
743
758
|
}
|
|
744
759
|
// T49 — resume di una conversazione in una nuova tab. Unico punto: lo chiamano
|
|
745
760
|
// il `⏎` della lista sessioni e quello sulla riga-sessione della ricerca, che
|
|
@@ -1050,15 +1065,72 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
1050
1065
|
return;
|
|
1051
1066
|
// Le superfici esistono solo nella lista: un modo capturing prende il
|
|
1052
1067
|
// frame intero e a quella riga c'è dell'altro.
|
|
1053
|
-
if (mode !== 'normal'
|
|
1068
|
+
if (mode !== 'normal')
|
|
1054
1069
|
return;
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1070
|
+
if (ev.row === LAUNCH_ROW) {
|
|
1071
|
+
const hit = hitRegion(launchRegions, ev.col);
|
|
1072
|
+
// Il click NON chiama l'azione: rientra dalla porta della tastiera col
|
|
1073
|
+
// tasto che la superficie annuncia. Un ramo nuovo su `t`/`c`/cifre nasce
|
|
1074
|
+
// così già cliccabile, e non esiste un secondo posto in cui il click possa
|
|
1075
|
+
// dire una cosa diversa dal tasto che gli sta scritto sopra.
|
|
1076
|
+
if (hit)
|
|
1077
|
+
onKey(hit, NO_MODIFIERS);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
onListClick(ev);
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* T21 (mandata 3) — click su un elemento di lista = SOLO FUOCO (D2): il pane
|
|
1084
|
+
* prende il focus e la selezione va sull'elemento, nessuna azione parte. Qui
|
|
1085
|
+
* il click non sintetizza un tasto, perché nessun tasto nomina una riga o una
|
|
1086
|
+
* vista — `↑↓` e `tab` sono relativi — e chiama i setter che la tastiera
|
|
1087
|
+
* usa a sua volta. Un click sulla vista già attiva si limita al focus.
|
|
1088
|
+
*
|
|
1089
|
+
* ECCEZIONE: la riga già selezionata col pane già a fuoco. Lì un tasto che
|
|
1090
|
+
* la nomina ESISTE — `⏎`, che agisce proprio su «la riga selezionata» — e il
|
|
1091
|
+
* click lo sintetizza, come le superfici della riga launch: due click sulla
|
|
1092
|
+
* stessa riga aprono il detail della task o fanno il resume della
|
|
1093
|
+
* conversazione, e ogni caso limite (pin stale, nessuna sessione) resta per
|
|
1094
|
+
* costruzione quello del tasto. Le righe meta ne sono fuori: `⏎` su di loro
|
|
1095
|
+
* non fa nulla, e un click che lo sintetizzasse produrrebbe solo la nota di
|
|
1096
|
+
* scarto di `selectedTaskOr`.
|
|
1097
|
+
*/
|
|
1098
|
+
function onListClick(ev) {
|
|
1099
|
+
if (!listGeometry)
|
|
1100
|
+
return;
|
|
1101
|
+
const hit = listHit(ev, listGeometry);
|
|
1102
|
+
if (!hit)
|
|
1103
|
+
return;
|
|
1104
|
+
if (hit.target === 'view') {
|
|
1105
|
+
setFocus(hit.pane);
|
|
1106
|
+
if (hit.pane === 'tasks')
|
|
1107
|
+
selectTaskView(hit.key);
|
|
1108
|
+
else
|
|
1109
|
+
selectSessionView(hit.key);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (hit.pane === 'tasks') {
|
|
1113
|
+
// Le due righe meta hanno indice fisso; le task riportano l'indice di
|
|
1114
|
+
// finestra a quello della lista completa, su cui è keyata la selezione.
|
|
1115
|
+
const index = hit.index < META_ROWS ? hit.index : taskWin.start + hit.index;
|
|
1116
|
+
if (focus === 'tasks' && index === selIndex && index >= META_ROWS) {
|
|
1117
|
+
onKey('', { ...NO_MODIFIERS, return: true });
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
setFocus('tasks');
|
|
1121
|
+
selectTaskRow(index);
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
const row = windowRows[hit.index];
|
|
1125
|
+
if (!row || row.kind === 'separator')
|
|
1126
|
+
return;
|
|
1127
|
+
if (focus === 'sessions' && row.sessionId === selSessionId) {
|
|
1128
|
+
onKey('', { ...NO_MODIFIERS, return: true });
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
setFocus('sessions');
|
|
1132
|
+
setSelSessionId(row.sessionId);
|
|
1133
|
+
}
|
|
1062
1134
|
}
|
|
1063
1135
|
function onKey(input, key) {
|
|
1064
1136
|
// T116 — `^C` sta SOPRA il dispatch dei modi, unico tasto a scavalcarlo.
|
|
@@ -1544,6 +1616,22 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
1544
1616
|
const selRowIndex = rowIndexOf(sessionRows, selSessionId);
|
|
1545
1617
|
const sessionWin = windowRange(sessionRows.length, selRowIndex, budget.sessionRows);
|
|
1546
1618
|
const windowRows = sessionRows.slice(sessionWin.start, sessionWin.end);
|
|
1619
|
+
// T21 — geometria delle liste per l'hit-test del click, dalla STESSA
|
|
1620
|
+
// aritmetica che disegna i pane: le parti degli header escono dal modulo che
|
|
1621
|
+
// `ui/panes.tsx` consuma per renderle, le righe cliccabili sono le finestre
|
|
1622
|
+
// appena calcolate. `null` in compatto: lì i pane non esistono.
|
|
1623
|
+
const spans = paneSpans(columns);
|
|
1624
|
+
const listGeometry = budget.compact
|
|
1625
|
+
? null
|
|
1626
|
+
: {
|
|
1627
|
+
columns,
|
|
1628
|
+
taskHeader: inlineRegions(headerItems(taskHeaderParts(taskCounts, taskViewId, taskWin.start, paneTasks.length - taskWin.end, columns)), spans.tasks.start + PANE_TEXT_PAD),
|
|
1629
|
+
sessionHeader: inlineRegions(headerItems(sessionHeaderParts(parentLabel, sessionCounts, sessionViewId, sessionWin.start, sessionRows.length - sessionWin.end, columns)), spans.sessions.start + PANE_TEXT_PAD),
|
|
1630
|
+
// Con un errore di caricamento al posto delle task c'è la riga rossa:
|
|
1631
|
+
// restano cliccabili le sole righe meta.
|
|
1632
|
+
taskRows: META_ROWS + (loadError ? 0 : windowTasks.length),
|
|
1633
|
+
sessionRows: windowRows.length,
|
|
1634
|
+
};
|
|
1547
1635
|
// Sotto la soglia minima il layout a box non entra a nessun costo: si scende
|
|
1548
1636
|
// a una riga sola. Perdere il deck per un terminale basso è meglio che
|
|
1549
1637
|
// sporcare la cronologia del terminale a ogni poll.
|
package/dist/mouse.js
CHANGED
|
@@ -199,3 +199,87 @@ export const LAUNCH_ROW = 4;
|
|
|
199
199
|
/** Colonna (1-based) del primo carattere di testo dentro il box esterno:
|
|
200
200
|
* bordo + `paddingX={1}`. */
|
|
201
201
|
export const FRAME_TEXT_COL = 3;
|
|
202
|
+
// ── Hit-test delle liste (mandata 3) ────────────────────────────────────────
|
|
203
|
+
//
|
|
204
|
+
// Click su un elemento di lista = SOLO FUOCO (D2): la selezione si sposta, non
|
|
205
|
+
// parte nessuna azione. A differenza della riga launch il click qui non può
|
|
206
|
+
// rientrare dalla porta della tastiera — non esiste un tasto che dica «vai alla
|
|
207
|
+
// riga 7» o «vista `nascoste`»: `↑↓` e `tab` sono relativi alla posizione
|
|
208
|
+
// corrente. Il chiamante chiama quindi il setter della selezione, come la
|
|
209
|
+
// rotella chiama lo `scroll` del modo.
|
|
210
|
+
/**
|
|
211
|
+
* Righe fisse dei due pane in modalità normale, contate dall'alto come
|
|
212
|
+
* `LAUNCH_ROW` e per la stessa ragione: sopra di loro non c'è niente di
|
|
213
|
+
* condizionale. Sotto la riga launch stanno il `marginTop` del blocco pane
|
|
214
|
+
* (riga 5), il bordo superiore (6), l'header con le viste (7) e il corpo (8+).
|
|
215
|
+
*/
|
|
216
|
+
export const PANE_HEADER_ROW = LAUNCH_ROW + 3;
|
|
217
|
+
export const PANE_BODY_ROW = PANE_HEADER_ROW + 1;
|
|
218
|
+
/** Nel pane task fra header e lista sta la riga sort/filtri, che non si
|
|
219
|
+
* seleziona: la prima riga di lista (`≡ tutte`) è quella dopo. */
|
|
220
|
+
export const TASK_LIST_ROW = PANE_BODY_ROW + 1;
|
|
221
|
+
/** Colonne fra il bordo di un pane e il suo testo: bordo + `paddingX={1}`. */
|
|
222
|
+
export const PANE_TEXT_PAD = 2;
|
|
223
|
+
/**
|
|
224
|
+
* Colonne (1-based, estremi inclusi, bordi compresi) dei due pane affiancati.
|
|
225
|
+
*
|
|
226
|
+
* Entrambi dichiarano `width="50%"` dell'interno del box esterno (`columns - 4`)
|
|
227
|
+
* e il primo porta `marginRight={1}`: la somma sfora di una colonna e Yoga la
|
|
228
|
+
* toglie per shrink, metà a testa, poi arrotonda le posizioni. L'esito
|
|
229
|
+
* misurato sotto pty è che il pane task è largo `floor((columns - 4) / 2)` —
|
|
230
|
+
* lo stesso conto di `paneTextWidth` più i suoi 4 di cornice — e il pane
|
|
231
|
+
* sessioni comincia dopo la colonna di margine e arriva al padding del box
|
|
232
|
+
* esterno. La colonna di margine non appartiene a nessuno dei due.
|
|
233
|
+
*/
|
|
234
|
+
export function paneSpans(columns) {
|
|
235
|
+
const inner = (columns || 80) - 4;
|
|
236
|
+
const taskW = Math.floor(inner / 2);
|
|
237
|
+
const tasksEnd = FRAME_TEXT_COL + taskW - 1;
|
|
238
|
+
return {
|
|
239
|
+
tasks: { start: FRAME_TEXT_COL, end: tasksEnd },
|
|
240
|
+
sessions: { start: tasksEnd + 2, end: FRAME_TEXT_COL + inner - 1 },
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Colonne delle parti di una riga che portano il proprio separatore IN TESTA
|
|
245
|
+
* (gli header dei pane: ` · 68 nascoste`). La regione di una parte keyed
|
|
246
|
+
* esclude il separatore: un click sul ` · ` fra due viste non deve sceglierne
|
|
247
|
+
* una. Le parti con `key` null (nome del pane, `↑N`/`↓N`) consumano colonne ma
|
|
248
|
+
* non producono regione; quelle vuote (cadute al taglio) non consumano niente.
|
|
249
|
+
*/
|
|
250
|
+
export function inlineRegions(parts, startCol) {
|
|
251
|
+
const out = [];
|
|
252
|
+
let col = startCol;
|
|
253
|
+
for (const p of parts) {
|
|
254
|
+
const w = termWidth(p.text);
|
|
255
|
+
if (w === 0)
|
|
256
|
+
continue;
|
|
257
|
+
if (p.key !== null) {
|
|
258
|
+
const lead = termWidth(p.text.match(/^[\s·]+/)?.[0] ?? '');
|
|
259
|
+
if (w > lead)
|
|
260
|
+
out.push({ key: p.key, start: col + lead, end: col + w - 1 });
|
|
261
|
+
}
|
|
262
|
+
col += w;
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
/** L'elemento di lista sotto il click, o `null` se il click cade su cornice,
|
|
267
|
+
* riga sort, spazio vuoto sotto la lista o fuori dai pane. */
|
|
268
|
+
export function listHit(ev, g) {
|
|
269
|
+
const spans = paneSpans(g.columns);
|
|
270
|
+
const inTasks = ev.col >= spans.tasks.start && ev.col <= spans.tasks.end;
|
|
271
|
+
const inSessions = ev.col >= spans.sessions.start && ev.col <= spans.sessions.end;
|
|
272
|
+
if (!inTasks && !inSessions)
|
|
273
|
+
return null;
|
|
274
|
+
const pane = inTasks ? 'tasks' : 'sessions';
|
|
275
|
+
if (ev.row === PANE_HEADER_ROW) {
|
|
276
|
+
const key = hitRegion(inTasks ? g.taskHeader : g.sessionHeader, ev.col);
|
|
277
|
+
return key ? { pane, target: 'view', key } : null;
|
|
278
|
+
}
|
|
279
|
+
const first = inTasks ? TASK_LIST_ROW : PANE_BODY_ROW;
|
|
280
|
+
const count = inTasks ? g.taskRows : g.sessionRows;
|
|
281
|
+
const index = ev.row - first;
|
|
282
|
+
if (index < 0 || index >= count)
|
|
283
|
+
return null;
|
|
284
|
+
return { pane, target: 'row', index };
|
|
285
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// T21 (mandata 3) — le PARTI degli header dei due pane come DATO, non come JSX.
|
|
2
|
+
//
|
|
3
|
+
// Stesso principio della riga launch (`launchSegments` in `cli.tsx`): gli
|
|
4
|
+
// stessi oggetti compongono la riga disegnata (`ui/panes.tsx`) e le colonne
|
|
5
|
+
// dell'hit-test (`cli.tsx`). Chi li deriva dall'altro — ri-splittando la
|
|
6
|
+
// stringa renderizzata, o ricontando le voci del catalogo — tiene due conti
|
|
7
|
+
// paralleli che divergono alla prima voce che cambia testo o ordine.
|
|
8
|
+
//
|
|
9
|
+
// Modulo PURO: nessun import da ink/react. Il taglio al budget (`cutParts`) sta
|
|
10
|
+
// qui e non nel componente perché è il taglio a decidere quali parti esistono a
|
|
11
|
+
// schermo, e una parte caduta non deve rispondere a un click.
|
|
12
|
+
import { cutParts } from './width.js';
|
|
13
|
+
import { paneTextWidth } from './layout.js';
|
|
14
|
+
import { SESSION_VIEWS, TASK_VIEWS, } from './pane-views.js';
|
|
15
|
+
function finish(parts, columns) {
|
|
16
|
+
const shown = cutParts(parts.map((p) => p.text), paneTextWidth(columns), parts.findIndex((p) => p.active));
|
|
17
|
+
return { parts, shown };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Header del pane task: le tre viste del catalogo, poi `↑N`/`↓N`.
|
|
21
|
+
* Le voci ci sono tutte anche a 0 (T100/D1) e l'attiva ha la precedenza sul
|
|
22
|
+
* budget (D6); i contatori di scroll stanno in coda perché sono i primi a
|
|
23
|
+
* cedere il posto su un terminale stretto.
|
|
24
|
+
*/
|
|
25
|
+
export function taskHeaderParts(counts, active, above, below, columns) {
|
|
26
|
+
const views = TASK_VIEWS.map((v, i) => ({
|
|
27
|
+
key: v.id,
|
|
28
|
+
text: `${i > 0 ? ' · ' : ''}${v.label(counts)}`,
|
|
29
|
+
color: v.color,
|
|
30
|
+
dim: Boolean(v.dim) || v.count(counts) === 0,
|
|
31
|
+
active: v.id === active,
|
|
32
|
+
}));
|
|
33
|
+
return finish([
|
|
34
|
+
...views,
|
|
35
|
+
{ key: null, text: above > 0 ? ` · ↑${above}` : '', dim: true, active: false },
|
|
36
|
+
{ key: null, text: below > 0 ? ` · ↓${below}` : '', dim: true, active: false },
|
|
37
|
+
], columns);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Header del pane sessioni: `Sessions` nomina il pane e non è una voce del
|
|
41
|
+
* catalogo (non si seleziona), poi le quattro viste, poi `↑N`/`↓N`.
|
|
42
|
+
*/
|
|
43
|
+
export function sessionHeaderParts(parentLabel, counts, active, above, below, columns) {
|
|
44
|
+
const views = SESSION_VIEWS.map((v) => ({
|
|
45
|
+
key: v.id,
|
|
46
|
+
text: ` · ${v.label(counts, parentLabel)}`,
|
|
47
|
+
color: v.color,
|
|
48
|
+
dim: Boolean(v.dim) || v.count(counts) === 0,
|
|
49
|
+
active: v.id === active,
|
|
50
|
+
}));
|
|
51
|
+
return finish([
|
|
52
|
+
{ key: null, text: 'Sessions', dim: false, active: false },
|
|
53
|
+
...views,
|
|
54
|
+
{ key: null, text: above > 0 ? ` · ↑${above}` : '', dim: true, active: false },
|
|
55
|
+
{ key: null, text: below > 0 ? ` · ↓${below}` : '', dim: true, active: false },
|
|
56
|
+
], columns);
|
|
57
|
+
}
|
|
58
|
+
/** Le parti COME SONO A SCHERMO — testo tagliato, chiave intatta — nella forma
|
|
59
|
+
* che `inlineRegions` (`mouse.ts`) misura per l'hit-test. */
|
|
60
|
+
export function headerItems(h) {
|
|
61
|
+
return h.parts.map((p, i) => ({ key: p.key, text: h.shown[i] ?? '' }));
|
|
62
|
+
}
|
package/dist/ui/panes.js
CHANGED
|
@@ -3,13 +3,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
// header. Sono presentazionali puri: ricevono la vista già selezionata e le
|
|
4
4
|
// larghezze già calcolate, non li derivano.
|
|
5
5
|
import { Box, Text } from 'ink';
|
|
6
|
-
import { cut,
|
|
6
|
+
import { cut, pad, sanitize, termWidth } from '../width.js';
|
|
7
7
|
import { paneTextWidth } from '../layout.js';
|
|
8
8
|
import { CARET, CARET_OFF, LIVE_BUSY, LIVE_IDLE, LIVE_NONE, MODEL_W, SESSION_SEP, SID_CHARS, TASK_EMPTY, WARN, metaCount, modelShort, relTime, } from '../glyphs.js';
|
|
9
9
|
import { TaskRow } from './task-row.js';
|
|
10
10
|
import { META_ROWS, ROW_ALL, ROW_SPOT } from '../model.js';
|
|
11
11
|
import { rowLabel, sessionTitle } from '../session-list.js';
|
|
12
|
-
import { sessionView, taskView,
|
|
12
|
+
import { sessionView, taskView, } from '../pane-views.js';
|
|
13
|
+
import { sessionHeaderParts, taskHeaderParts } from '../pane-header.js';
|
|
13
14
|
import { describeSort, PRI_ENTRIES, PROG_ENTRIES, } from '../view.js';
|
|
14
15
|
/**
|
|
15
16
|
* Header del pane task, tagliato QUI e non da Ink — stesso motivo del gemello
|
|
@@ -31,24 +32,16 @@ import { describeSort, PRI_ENTRIES, PROG_ENTRIES, } from '../view.js';
|
|
|
31
32
|
* su un terminale stretto — e la voce attiva ha la precedenza sul budget (D6).
|
|
32
33
|
*/
|
|
33
34
|
export function TasksHeader({ counts, active, above, below, focused, columns, }) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
const segments = [
|
|
46
|
-
...views,
|
|
47
|
-
{ text: above > 0 ? ` · ↑${above}` : '', dim: true, active: false, color: undefined },
|
|
48
|
-
{ text: below > 0 ? ` · ↓${below}` : '', dim: true, active: false, color: undefined },
|
|
49
|
-
];
|
|
50
|
-
const shown = cutParts(segments.map((s) => s.text), paneTextWidth(columns), segments.findIndex((s) => s.active));
|
|
51
|
-
return (_jsx(Text, { bold: true, color: focused ? 'cyan' : undefined, wrap: "truncate-end", children: segments.map((seg, i) => shown[i] ? (_jsx(Text, { color: seg.color, dimColor: seg.dim, inverse: seg.active, children: shown[i] }, i)) : null) }));
|
|
35
|
+
// T21 — le parti e il loro taglio vengono da `pane-header.ts`, lo stesso
|
|
36
|
+
// modulo da cui `cli.tsx` ricava le colonne cliccabili: una fonte sola.
|
|
37
|
+
const { parts, shown } = taskHeaderParts(counts, active, above, below, columns);
|
|
38
|
+
return _jsx(HeaderLine, { parts: parts, shown: shown, focused: focused });
|
|
39
|
+
}
|
|
40
|
+
/** Resa di un header già tagliato: ogni parte col suo stile, l'attiva in
|
|
41
|
+
* video inverso (T100/D5 — costa 0 colonne e non entra in gara con la
|
|
42
|
+
* semantica di colore già occupata). */
|
|
43
|
+
function HeaderLine({ parts, shown, focused, }) {
|
|
44
|
+
return (_jsx(Text, { bold: true, color: focused ? 'cyan' : undefined, wrap: "truncate-end", children: parts.map((seg, i) => shown[i] ? (_jsx(Text, { color: seg.color, dimColor: seg.dim, inverse: seg.active, children: shown[i] }, i)) : null) }));
|
|
52
45
|
}
|
|
53
46
|
export function TasksPane({ tasks, counts, activeView, paneCount, view, selected, spotCount, allCount, idW, tailW, focused, loadError, windowStart, above, below, columns, data, }) {
|
|
54
47
|
const allSelected = selected === ROW_ALL;
|
|
@@ -87,25 +80,8 @@ export function TasksPane({ tasks, counts, activeView, paneCount, view, selected
|
|
|
87
80
|
* pinnate dal resto dell'header e non è decorazione.
|
|
88
81
|
*/
|
|
89
82
|
export function SessionsHeader({ parentLabel, counts, active, above, below, focused, columns, }) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
text: ` · ${v.label(counts, parentLabel)}`,
|
|
94
|
-
color: v.color,
|
|
95
|
-
dim: v.dim || n === 0,
|
|
96
|
-
active: v.id === active,
|
|
97
|
-
};
|
|
98
|
-
});
|
|
99
|
-
const segments = [
|
|
100
|
-
// `Sessions` non è una voce del catalogo: nomina il pane, non un
|
|
101
|
-
// sottoinsieme, quindi non è raggiungibile con le frecce.
|
|
102
|
-
{ text: 'Sessions', color: undefined, dim: false, active: false },
|
|
103
|
-
...views,
|
|
104
|
-
{ text: above > 0 ? ` · ↑${above}` : '', dim: true, active: false, color: undefined },
|
|
105
|
-
{ text: below > 0 ? ` · ↓${below}` : '', dim: true, active: false, color: undefined },
|
|
106
|
-
];
|
|
107
|
-
const shown = cutParts(segments.map((s) => s.text), paneTextWidth(columns), segments.findIndex((s) => s.active));
|
|
108
|
-
return (_jsx(Text, { bold: true, color: focused ? 'cyan' : undefined, wrap: "truncate-end", children: segments.map((seg, i) => shown[i] ? (_jsx(Text, { color: seg.color, dimColor: seg.dim, inverse: seg.active, children: shown[i] }, i)) : null) }));
|
|
83
|
+
const { parts, shown } = sessionHeaderParts(parentLabel, counts, active, above, below, columns);
|
|
84
|
+
return _jsx(HeaderLine, { parts: parts, shown: shown, focused: focused });
|
|
109
85
|
}
|
|
110
86
|
export function SessionsPane({ parentLabel, isSpot, isAll, bindings, taskW, ageW, rows, counts, activeView, paneCount, selectedId, focused, above, below, columns, forkOf, sessionNotes, projectCore, live, }) {
|
|
111
87
|
return (_jsxs(Box, { flexDirection: "column", width: "50%", borderStyle: "single", borderColor: focused ? 'cyan' : 'gray', paddingX: 1, children: [_jsx(SessionsHeader, { parentLabel: parentLabel, counts: counts, active: activeView, above: above, below: below, focused: focused, columns: columns }), paneCount === 0 ? (
|
package/package.json
CHANGED