@lamemind/loom-deck 0.51.0 → 0.52.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 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
- const next = Math.max(0, Math.min(paneTasks.length + META_ROWS - 1, selIndex + delta));
717
- if (next === ROW_ALL)
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 (next === ROW_SPOT)
724
+ else if (index === ROW_SPOT)
720
725
  setSel(SPOT);
721
726
  else
722
- setSel(paneTasks[next - META_ROWS]?.id ?? SPOT);
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
- const next = cycleTaskView(taskViewId, delta);
733
- setTaskViewId(next);
734
- setSel(ALL);
735
- setNote(`vista task: ${taskView(next).label(taskCounts)}`);
736
- }
737
- else {
738
- const next = cycleSessionView(sessionViewId, delta);
739
- setSessionViewId(next);
740
- setSelSessionId(null);
741
- setNote(`vista sessioni: ${sessionView(next).label(sessionCounts, parentLabel)}`);
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,50 @@ 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' || ev.row !== LAUNCH_ROW)
1068
+ if (mode !== 'normal')
1069
+ return;
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);
1054
1078
  return;
1055
- const hit = hitRegion(launchRegions, ev.col);
1056
- // Il click NON chiama l'azione: rientra dalla porta della tastiera col
1057
- // tasto che la superficie annuncia. Un ramo nuovo su `t`/`c`/cifre nasce
1058
- // così già cliccabile, e non esiste un secondo posto in cui il click possa
1059
- // dire una cosa diversa dal tasto che gli sta scritto sopra.
1060
- if (hit)
1061
- onKey(hit, NO_MODIFIERS);
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
+ function onListClick(ev) {
1090
+ if (!listGeometry)
1091
+ return;
1092
+ const hit = listHit(ev, listGeometry);
1093
+ if (!hit)
1094
+ return;
1095
+ setFocus(hit.pane);
1096
+ if (hit.pane === 'tasks') {
1097
+ if (hit.target === 'view')
1098
+ selectTaskView(hit.key);
1099
+ // Le due righe meta hanno indice fisso; le task riportano l'indice di
1100
+ // finestra a quello della lista completa, su cui è keyata la selezione.
1101
+ else
1102
+ selectTaskRow(hit.index < META_ROWS ? hit.index : taskWin.start + hit.index);
1103
+ }
1104
+ else if (hit.target === 'view') {
1105
+ selectSessionView(hit.key);
1106
+ }
1107
+ else {
1108
+ const row = windowRows[hit.index];
1109
+ if (row && row.kind !== 'separator')
1110
+ setSelSessionId(row.sessionId);
1111
+ }
1062
1112
  }
1063
1113
  function onKey(input, key) {
1064
1114
  // T116 — `^C` sta SOPRA il dispatch dei modi, unico tasto a scavalcarlo.
@@ -1544,6 +1594,22 @@ function Deck({ cwd, tasksPath, tasksDir }) {
1544
1594
  const selRowIndex = rowIndexOf(sessionRows, selSessionId);
1545
1595
  const sessionWin = windowRange(sessionRows.length, selRowIndex, budget.sessionRows);
1546
1596
  const windowRows = sessionRows.slice(sessionWin.start, sessionWin.end);
1597
+ // T21 — geometria delle liste per l'hit-test del click, dalla STESSA
1598
+ // aritmetica che disegna i pane: le parti degli header escono dal modulo che
1599
+ // `ui/panes.tsx` consuma per renderle, le righe cliccabili sono le finestre
1600
+ // appena calcolate. `null` in compatto: lì i pane non esistono.
1601
+ const spans = paneSpans(columns);
1602
+ const listGeometry = budget.compact
1603
+ ? null
1604
+ : {
1605
+ columns,
1606
+ taskHeader: inlineRegions(headerItems(taskHeaderParts(taskCounts, taskViewId, taskWin.start, paneTasks.length - taskWin.end, columns)), spans.tasks.start + PANE_TEXT_PAD),
1607
+ sessionHeader: inlineRegions(headerItems(sessionHeaderParts(parentLabel, sessionCounts, sessionViewId, sessionWin.start, sessionRows.length - sessionWin.end, columns)), spans.sessions.start + PANE_TEXT_PAD),
1608
+ // Con un errore di caricamento al posto delle task c'è la riga rossa:
1609
+ // restano cliccabili le sole righe meta.
1610
+ taskRows: META_ROWS + (loadError ? 0 : windowTasks.length),
1611
+ sessionRows: windowRows.length,
1612
+ };
1547
1613
  // Sotto la soglia minima il layout a box non entra a nessun costo: si scende
1548
1614
  // a una riga sola. Perdere il deck per un terminale basso è meglio che
1549
1615
  // 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, cutParts, pad, sanitize, termWidth } from '../width.js';
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, SESSION_VIEWS, TASK_VIEWS, } from '../pane-views.js';
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
- const views = TASK_VIEWS.map((v, i) => {
35
- const n = v.count(counts);
36
- return {
37
- // Il separatore sta nel segmento, non fra i segmenti: `cutParts` misura la
38
- // riga pezzo per pezzo e uno spazio fuori dai pezzi non verrebbe contato.
39
- text: `${i > 0 ? ' · ' : ''}${v.label(counts)}`,
40
- color: v.color,
41
- dim: v.dim || n === 0,
42
- active: v.id === active,
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 views = SESSION_VIEWS.map((v) => {
91
- const n = v.count(counts);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.51.0",
3
+ "version": "0.52.0",
4
4
  "description": "Deck TUI Ink per-progetto della famiglia loom: legge tasks.md e spawna sessioni Claude Code bound via LOOM_TASK",
5
5
  "type": "module",
6
6
  "bin": {