@lamemind/loom-deck 0.65.0 → 0.65.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/layout.js +17 -0
- package/dist/spawn.js +8 -6
- package/dist/ui/preview.js +9 -4
- package/package.json +1 -1
- package/scripts/deck-run +13 -13
package/dist/layout.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// da scalare va tolta in un posto solo, o due chiamanti tolgono cose diverse
|
|
4
4
|
// dalla stessa larghezza e il bordo viene mangiato.
|
|
5
5
|
import { stripProjectCore } from './session-list.js';
|
|
6
|
+
import { SID_CHARS } from './glyphs.js';
|
|
6
7
|
/**
|
|
7
8
|
* Lunghezza in CODE POINT. Il caret ci indicizza sopra: `.length` conterebbe le
|
|
8
9
|
* code unit UTF-16 e un'emoji nel titolo varrebbe 2 posizioni, cioè un cursore
|
|
@@ -96,6 +97,22 @@ export function assignTextWidth(columns) {
|
|
|
96
97
|
export function previewTextWidth(columns) {
|
|
97
98
|
return Math.max(10, (columns || 80) - 8);
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* I tre pezzi della riga d'apertura del blocco preview di una conversazione —
|
|
102
|
+
* sid, nota, titolo — ognuno col proprio separatore in coda, pronti per
|
|
103
|
+
* `cutParts` sul budget di `previewTextWidth`.
|
|
104
|
+
*
|
|
105
|
+
* Esistono come funzione, e il taglio passa da `cutParts`, per l'invariante ③
|
|
106
|
+
* (§`assignTextWidth` sopra): lasciare la riga a `wrap="truncate-end"` la fa
|
|
107
|
+
* tagliare da `cli-truncate`, che sfora di una colonna per emoji e mangia il
|
|
108
|
+
* bordo destro del box. Due dei tre pezzi portano testo libero — la nota la
|
|
109
|
+
* scrive l'utente, e un titolo di conversazione auto-generato apre con l'emoji
|
|
110
|
+
* dell'azione — quindi le emoji su questa riga sono il caso normale, non il
|
|
111
|
+
* limite.
|
|
112
|
+
*/
|
|
113
|
+
export function previewTitleParts(sessionId, note, title) {
|
|
114
|
+
return [`${sessionId.slice(0, SID_CHARS)} `, note ? `${note} ` : '', title];
|
|
115
|
+
}
|
|
99
116
|
/**
|
|
100
117
|
* Larghezza del TESTO dentro un pane al 50%: box esterno (2 bordi + 2 padding)
|
|
101
118
|
* → metà → bordo + padding del pane.
|
package/dist/spawn.js
CHANGED
|
@@ -209,6 +209,8 @@ export const ACTION_HOTKEYS = Object.fromEntries(DETAIL_ACTIONS.map((a, i) => [a
|
|
|
209
209
|
// `deck-run` lascia passare nel titolo della tab: la whitelist è una lista
|
|
210
210
|
// chiusa che nomina questi code point (P2 preflight), quindi cambiare una voce
|
|
211
211
|
// qui e non là riapre la divergenza fra titolo in lista e titolo in tab.
|
|
212
|
+
// Le quadre che delimitavano il prefisso sono cadute: l'emoji da sola si stacca
|
|
213
|
+
// già dallo slug, e con lei sono uscite dalla whitelist di `_sane_note`.
|
|
212
214
|
const ACTION_EMOJI = {
|
|
213
215
|
preflight: '📐',
|
|
214
216
|
run: '🚀',
|
|
@@ -218,7 +220,7 @@ const ACTION_EMOJI = {
|
|
|
218
220
|
checkpoint: '🏁',
|
|
219
221
|
};
|
|
220
222
|
/**
|
|
221
|
-
* Titolo di fallback per una conversazione con nota vuota: `
|
|
223
|
+
* Titolo di fallback per una conversazione con nota vuota: `{emoji} {slug}`,
|
|
222
224
|
* o il solo slug per `none` (D1/D2 preflight). Lo slug viene dal NOME del
|
|
223
225
|
* task file (`findTaskFile`), non dalla descrizione di `tasks.md`: il nome è
|
|
224
226
|
* già dentro l'alfabeto di `_sane_note` per costruzione — minuscolo, separato
|
|
@@ -226,10 +228,10 @@ const ACTION_EMOJI = {
|
|
|
226
228
|
* `/` che la riduzione toglie senza sostituto, saldando le parole (D1
|
|
227
229
|
* razionale).
|
|
228
230
|
*
|
|
229
|
-
* Il PREFISSO invece nell'alfabeto non ci sta per costruzione: emoji
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
231
|
+
* Il PREFISSO invece nell'alfabeto non ci sta per costruzione: l'emoji ci entra
|
|
232
|
+
* solo perché la whitelist di `_sane_note` le nomina una per una (T156). È
|
|
233
|
+
* l'unica parte del titolo di fallback che dipende da una modifica fatta
|
|
234
|
+
* dall'altro lato.
|
|
233
235
|
*
|
|
234
236
|
* `null` quando il task file non si trova: il chiamante decide se ripiegare
|
|
235
237
|
* su nota vuota o su un altro fallback.
|
|
@@ -240,7 +242,7 @@ export function fallbackTitle(tasksDir, id, kind) {
|
|
|
240
242
|
return null;
|
|
241
243
|
const slug = basename(path, '.md').slice(id.length + 1).replace(/-/g, ' ');
|
|
242
244
|
const emoji = ACTION_EMOJI[kind];
|
|
243
|
-
return emoji ?
|
|
245
|
+
return emoji ? `${emoji} ${slug}` : slug;
|
|
244
246
|
}
|
|
245
247
|
// Spawn detached: il deck spawna ma NON contiene la sessione (la possiede
|
|
246
248
|
// ptyxis-agent). unref + stdio ignore → ritorna subito, la TUI resta viva.
|
package/dist/ui/preview.js
CHANGED
|
@@ -2,9 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
// Blocco preview a piena larghezza sotto i due pane: mostra la task o la
|
|
3
3
|
// conversazione selezionata a seconda del pane a fuoco.
|
|
4
4
|
import { Box, Text } from 'ink';
|
|
5
|
-
import { cut, wrapLines } from '../width.js';
|
|
6
|
-
import { previewTextWidth } from '../layout.js';
|
|
7
|
-
import { LIVE_BUSY, LIVE_IDLE, META_KEYS,
|
|
5
|
+
import { cut, cutParts, wrapLines } from '../width.js';
|
|
6
|
+
import { previewTextWidth, previewTitleParts } from '../layout.js';
|
|
7
|
+
import { LIVE_BUSY, LIVE_IDLE, META_KEYS, fmtDateTime, fmtSize, modelAlias, } from '../glyphs.js';
|
|
8
8
|
import { NATURA_SHORT, inboxMark } from '../inbox.js';
|
|
9
9
|
import { ChoiceRow } from './fields.js';
|
|
10
10
|
import { MODELS } from '../spawn.js';
|
|
@@ -64,7 +64,12 @@ export function SessionPreview({ s, firstLines, lastLines, columns, origin, note
|
|
|
64
64
|
// non c'è una provenienza vera da dichiarare.
|
|
65
65
|
const originAlias = modelAlias(s.model);
|
|
66
66
|
const originIndex = originAlias ? MODELS.indexOf(originAlias) : undefined;
|
|
67
|
-
|
|
67
|
+
// La riga del titolo si taglia QUI, con la contabilità del deck, invece di
|
|
68
|
+
// lasciarla a `wrap="truncate-end"`: Ink taglia con `cli-truncate`, che sfora
|
|
69
|
+
// di una colonna per emoji (invariante ③ di width.ts) e mangia il bordo del
|
|
70
|
+
// box. Pezzi e ragione stanno in `previewTitleParts`.
|
|
71
|
+
const [sidPart, notePart, titlePart] = cutParts(previewTitleParts(s.sessionId, note, s.title), previewTextWidth(columns));
|
|
72
|
+
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 ", fmtDateTime(s.ts), " \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}`)))] }));
|
|
68
73
|
}
|
|
69
74
|
/**
|
|
70
75
|
* Righe non-wrappabili del dettaglio (titolo + meta + commit) e loro conteggio.
|
package/package.json
CHANGED
package/scripts/deck-run
CHANGED
|
@@ -116,9 +116,9 @@ MODEL=""
|
|
|
116
116
|
# tab. È l'UNICO ingresso di testo libero nel titolo — gli altri componenti
|
|
117
117
|
# (emoji/name dal file committato, TaskID) sono controllati — quindi il testo non
|
|
118
118
|
# si quota, si RIDUCE a un alfabeto sicuro (vedi _sane_note): tutto ciò che non
|
|
119
|
-
# ci rientra sparisce, apici inclusi. L'alfabeto ammette
|
|
120
|
-
#
|
|
121
|
-
#
|
|
119
|
+
# ci rientra sparisce, apici inclusi. L'alfabeto ammette quattro emoji (T156)
|
|
120
|
+
# perché il deck ci fa passare il prefisso del titolo di fallback (`🚀 slug`);
|
|
121
|
+
# ogni altra emoji cade ancora.
|
|
122
122
|
# --model <fable|opus|sonnet|haiku> (T108): modello della sessione. Enum e non
|
|
123
123
|
# stringa libera perché il valore finisce dentro `bash -lc` e un refuso
|
|
124
124
|
# produrrebbe una tab con un comando che il CLI rifiuta. Le voci sono ALIAS e
|
|
@@ -269,22 +269,22 @@ WORKDIR="${LOOM_DECK_WORKDIR:-$PWD}"
|
|
|
269
269
|
# titolo finisce dentro apici singoli in `bash -lc "claude --name '…'"`, e la
|
|
270
270
|
# nota è testo digitato a mano — enumerare i caratteri pericolosi significa
|
|
271
271
|
# sbagliarne uno, enumerare quelli ammessi no. Restano lettere, cifre, accentate
|
|
272
|
-
# italiane, spazio, trattino, underscore
|
|
273
|
-
#
|
|
272
|
+
# italiane, spazio, trattino, underscore e quattro emoji (sotto); tutto il resto
|
|
273
|
+
# (apici, backslash, `$`, backtick, `;`, quadre, ogni altra emoji…) cade.
|
|
274
274
|
#
|
|
275
|
-
# T156 — le
|
|
276
|
-
#
|
|
277
|
-
# `
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
275
|
+
# T156 — le quattro emoji entrano per il PREFISSO del titolo di fallback che il
|
|
276
|
+
# deck genera quando il campo nota è vuoto (`🚀 slug`, `ACTION_EMOJI` in
|
|
277
|
+
# `src/spawn.ts`). Senza di loro il prefisso spariva qui e restava nella lista
|
|
278
|
+
# del deck, che riceve la nota grezza dal sidecar `session-tasks.jsonl`: la
|
|
279
|
+
# stessa conversazione portava due titoli diversi, senza errore. Le quadre che
|
|
280
|
+
# delimitavano il prefisso sono cadute col prefisso stesso: l'emoji da sola si
|
|
281
|
+
# stacca già dallo slug.
|
|
281
282
|
# La whitelist le nomina una per una e NON apre un intervallo di code point. Due
|
|
282
283
|
# ragioni: un'emoji arriva nella tab solo se il deck la mostra identica in lista,
|
|
283
284
|
# cioè se `sanitize` (`src/width.ts`) la lascia passare invece di renderla `·`, e
|
|
284
285
|
# quel verdetto si verifica un glifo alla volta (🗺️ è astrale e finisce comunque
|
|
285
286
|
# in `·`); e l'ordine di un intervallo dentro un bracket expression segue la
|
|
286
287
|
# collazione della locale, non i code point.
|
|
287
|
-
# `]` sta per PRIMO nel set negato, dove chiude la classe invece di aprirla.
|
|
288
288
|
#
|
|
289
289
|
# Il cap a 60 CHARACTER è largo di proposito: una tab che sfora tronca a destra
|
|
290
290
|
# da sola, quindi il limite serve solo a non spingere fuori vista la parte del
|
|
@@ -305,7 +305,7 @@ _sane_note() { # <raw> → nota ridotta, spazi collassati, cap 60 char
|
|
|
305
305
|
# `tr` non ne ha bisogno: squeeza uno spazio ASCII, e 0x20 non compare mai
|
|
306
306
|
# come byte di continuazione di una sequenza UTF-8.
|
|
307
307
|
s="$(printf '%s' "$1" \
|
|
308
|
-
| LC_ALL=C.UTF-8 sed 's/[^
|
|
308
|
+
| LC_ALL=C.UTF-8 sed 's/[^A-Za-z0-9 _àèéìòùÀÈÉÌÒÙ📐🚀📊🏁-]//g' \
|
|
309
309
|
| tr -s ' ')"
|
|
310
310
|
s="${s# }"; s="${s% }"
|
|
311
311
|
printf '%s' "${s:0:60}"
|