@lamemind/loom-deck 0.70.0 → 0.71.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 +55 -0
- package/dist/cli.js +39 -13
- package/dist/deck-model.js +113 -31
- package/dist/doc-tree.js +468 -0
- package/dist/doc-views.js +117 -0
- package/dist/frame.js +51 -23
- package/dist/hooks.js +45 -0
- package/dist/inbox.js +10 -1
- package/dist/input-modes.js +38 -1
- package/dist/input.js +90 -21
- package/dist/model.js +11 -0
- package/dist/mouse.js +18 -8
- package/dist/overlays/file-sheet.js +101 -0
- package/dist/pane-header.js +42 -0
- package/dist/sane-note.js +4 -4
- package/dist/spawn-catalog.js +40 -4
- package/dist/ui/doc-pane.js +85 -0
- package/dist/ui/file-sheet-screen.js +58 -0
- package/dist/ui/header-line.js +19 -0
- package/dist/ui/inbox-pane.js +83 -0
- package/dist/ui/panes.js +13 -74
- package/dist/ui/preview.js +25 -1
- package/dist/ui/screens.js +16 -4
- package/dist/viewport.js +41 -18
- package/package.json +1 -1
- package/scripts/deck-run +6 -5
package/dist/ui/preview.js
CHANGED
|
@@ -6,6 +6,7 @@ import { cut, cutParts, wrapLines } from '../width.js';
|
|
|
6
6
|
import { previewTextWidth, previewTitleParts } from '../layout.js';
|
|
7
7
|
import { LIVE_BUSY, LIVE_IDLE, META_KEYS, fmtDateTime, fmtSize, modelAlias, } from '../glyphs.js';
|
|
8
8
|
import { NATURA_SHORT, inboxMark } from '../inbox.js';
|
|
9
|
+
import { flagNames } from '../doc-tree.js';
|
|
9
10
|
import { ChoiceRow } from './fields.js';
|
|
10
11
|
import { MODELS } from '../spawn-catalog.js';
|
|
11
12
|
/** Colonne del prefisso delle due anteprime della preview sessione (`» `, `« `,
|
|
@@ -14,7 +15,7 @@ import { MODELS } from '../spawn-catalog.js';
|
|
|
14
15
|
* un bordo mangiato. */
|
|
15
16
|
const PREVIEW_PREFIX_W = 2;
|
|
16
17
|
export function PreviewPane(p) {
|
|
17
|
-
return (_jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: p.kind === 'task' ? (_jsx(TaskPreview, { detail: p.detail, maxLines: p.maxLines, columns: p.columns })) : p.kind === 'inbox' ? (_jsx(InboxPreview, { file: p.file, columns: p.columns })) : (_jsx(SessionPreview, { s: p.s, firstLines: p.firstLines, lastLines: p.lastLines, columns: p.columns, origin: p.origin, note: p.note, live: p.live, resumeModel: p.resumeModel })) }));
|
|
18
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: p.kind === 'task' ? (_jsx(TaskPreview, { detail: p.detail, maxLines: p.maxLines, columns: p.columns })) : p.kind === 'inbox' ? (_jsx(InboxPreview, { file: p.file, columns: p.columns })) : p.kind === 'doc' ? (_jsx(DocPreview, { row: p.row, columns: p.columns })) : (_jsx(SessionPreview, { s: p.s, firstLines: p.firstLines, lastLines: p.lastLines, columns: p.columns, origin: p.origin, note: p.note, live: p.live, resumeModel: p.resumeModel })) }));
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
21
|
* T134 — corpo della preview inbox: due righe FISSE, contate da
|
|
@@ -40,6 +41,29 @@ export function InboxPreview({ file, columns }) {
|
|
|
40
41
|
: 'in coda: una skill può prenderlo da sola';
|
|
41
42
|
return (_jsxs(_Fragment, { 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: [file.nozioni, " nozioni \u00B7 ", file.aperte, " aperte \u00B7 ", fmtSize(file.chars), " \u00B7", ' ', fmtDateTime(file.created * 1000), file.cappello ? ` · ${file.cappello}` : '', file.indexed ? ' · indexed' : '', " \u00B7 ", state] })] }));
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* T160 — corpo della preview doc: due righe FISSE, contate da
|
|
46
|
+
* `DOC_DETAIL_FIXED`. Ogni riga aggiunta va scalata lì, o il frame sfonda `rows`
|
|
47
|
+
* e Ink passa a `clearTerminal`.
|
|
48
|
+
*
|
|
49
|
+
* Porta le due cose che la riga di lista non può (P6): il PATH intero — in lista
|
|
50
|
+
* l'albero mostra l'ultimo segmento e le viste piatte troncano — e i NOMI INTERI
|
|
51
|
+
* dei flag accanto alle cifre che li spiegano. Le short di riga dicono quale
|
|
52
|
+
* flag; qui si legge perché ce l'ha, che è il dato su cui si decide se lanciare
|
|
53
|
+
* l'operazione.
|
|
54
|
+
*
|
|
55
|
+
* La riga meta cambia di natura fra file e cartella e non è un ramo di
|
|
56
|
+
* cortesia: `char` di un file è la sua lunghezza, `char` di una cartella è la
|
|
57
|
+
* somma dei suoi — due misure che si confrontano con due soglie diverse, e
|
|
58
|
+
* mostrarle con la stessa etichetta le farebbe leggere come la stessa cosa.
|
|
59
|
+
*/
|
|
60
|
+
export function DocPreview({ row, columns }) {
|
|
61
|
+
const width = previewTextWidth(columns);
|
|
62
|
+
const names = flagNames(row.flags);
|
|
63
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: row.kind === 'dir' ? 'dir ' : 'file' }), ' ', cut(row.path, Math.max(10, width - 6))] }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [row.kind === 'dir'
|
|
64
|
+
? `${row.files} file · ${fmtSize(row.chars)} in cartella`
|
|
65
|
+
: `${fmtSize(row.chars)} · TLDR ${row.tldr || 'assente'}`, ' · ', names.length > 0 ? names.join(' · ') : 'nessun flag: in equilibrio'] })] }));
|
|
66
|
+
}
|
|
43
67
|
// T49 — corpo della preview sessione. Tutti i campi vengono dal parse già
|
|
44
68
|
// cached dell'adapter (mtime-keyed): non costa I/O al movimento di selezione.
|
|
45
69
|
// Mostra "da dove parte, dove è arrivata": il primo prompt utente (`» `) e
|
package/dist/ui/screens.js
CHANGED
|
@@ -34,7 +34,7 @@ import { taskColumns } from '../view.js';
|
|
|
34
34
|
import { AssignScreen } from './assign-screen.js';
|
|
35
35
|
import { DetailScreen } from './detail-screen.js';
|
|
36
36
|
import { StatusScreen } from './status-screen.js';
|
|
37
|
-
import {
|
|
37
|
+
import { FileSheetScreen, docSheetHead, inboxSheetHead } from './file-sheet-screen.js';
|
|
38
38
|
import { WrapScreen } from './wrap-screen.js';
|
|
39
39
|
import { SpawnScreen } from './spawn-screen.js';
|
|
40
40
|
import { ReaderScreen, SearchScreen } from './search-screen.js';
|
|
@@ -108,7 +108,7 @@ export function HintBar({ mode, purge, keyLegend, indicators, columns, }) {
|
|
|
108
108
|
*/
|
|
109
109
|
export function screenFor(input) {
|
|
110
110
|
const { mode, rows, columns, note, overlays } = input;
|
|
111
|
-
const { assign, sheet, search, status, inbox, wrap, spawn } = overlays;
|
|
111
|
+
const { assign, sheet, search, status, inbox, doc, wrap, spawn } = overlays;
|
|
112
112
|
// ── T57 · schermata di assegnazione ─────────────────────────────────────
|
|
113
113
|
// Sostitutiva come ricerca e reader (D3): la lista task non entra in un box
|
|
114
114
|
// sopra i due pane, e prendendo l'intero frame non costa nulla al loro budget.
|
|
@@ -169,12 +169,24 @@ export function screenFor(input) {
|
|
|
169
169
|
// nozioni supera i 28KB, cioè è più lungo di ogni task file del progetto.
|
|
170
170
|
if (mode === 'inbox' && inbox.sheet) {
|
|
171
171
|
if (isCompact(inbox.capacity)) {
|
|
172
|
-
return (_jsx(CompactNotice, { what: inbox.sheet.
|
|
172
|
+
return (_jsx(CompactNotice, { what: inbox.sheet.item.basename, esc: "chiude", rows: rows, columns: columns }));
|
|
173
173
|
}
|
|
174
174
|
// Il clamp serve anche qui: un resize può accorciare il testo sotto uno
|
|
175
175
|
// scroll già dato.
|
|
176
176
|
const start = Math.min(inbox.top, inbox.maxTop);
|
|
177
|
-
return (_jsx(
|
|
177
|
+
return (_jsx(FileSheetScreen, { head: inboxSheetHead(inbox.sheet.item), missing: inbox.sheet.text === null, lines: inbox.lines.slice(start, start + inbox.capacity), spans: inbox.doc?.spans ?? [], top: start, total: inbox.lines.length, capacity: inbox.capacity, prompt: inbox.prompt, hint: inbox.hint, columns: columns }));
|
|
178
|
+
}
|
|
179
|
+
// ── T160 · detail di un bersaglio doc ───────────────────────────────────
|
|
180
|
+
// Nona schermata sostitutiva: la stessa dell'inbox (`useFileSheet`, due
|
|
181
|
+
// istanze) su un altro oggetto. Su una cartella il corpo porta l'elenco dei
|
|
182
|
+
// suoi file al posto di un testo (P10): il gesto è lo stesso, cambia cosa si
|
|
183
|
+
// legge.
|
|
184
|
+
if (mode === 'doc' && doc.sheet) {
|
|
185
|
+
if (isCompact(doc.capacity)) {
|
|
186
|
+
return _jsx(CompactNotice, { what: doc.sheet.item.path, esc: "chiude", rows: rows, columns: columns });
|
|
187
|
+
}
|
|
188
|
+
const start = Math.min(doc.top, doc.maxTop);
|
|
189
|
+
return (_jsx(FileSheetScreen, { head: docSheetHead(doc.sheet.item), missing: doc.sheet.text === null, lines: doc.lines.slice(start, start + doc.capacity), spans: doc.doc?.spans ?? [], top: start, total: doc.lines.length, capacity: doc.capacity, prompt: doc.prompt, hint: doc.hint, columns: columns }));
|
|
178
190
|
}
|
|
179
191
|
// ── T134 · lista hard-wrap ──────────────────────────────────────────────
|
|
180
192
|
// Settima schermata sostitutiva: sul cappello lo scan trova 77 `WRAP` e 147
|
package/dist/viewport.js
CHANGED
|
@@ -29,12 +29,17 @@ const MAX_DETAIL_LINES = 6;
|
|
|
29
29
|
// a un pane va scalata qui, o il frame sfonda `rows` e Ink passa a
|
|
30
30
|
// clearTerminal (frame-fantasma nello scrollback di VTE).
|
|
31
31
|
const TASKS_PANE_CHROME = 6; // 2 bordi + header "Tasks (n)" + riga sort + 2 righe meta
|
|
32
|
-
// T134 — vale per
|
|
33
|
-
// cornice (2 bordi + header) e
|
|
34
|
-
// `Budget.sessionRows` è la capienza
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
32
|
+
// T134/T160 — vale per TRE pane su quattro: conversazioni, coda inbox e albero
|
|
33
|
+
// doc hanno la stessa cornice (2 bordi + header) e ne sono montati al massimo
|
|
34
|
+
// due alla volta, quindi `Budget.sessionRows` è la capienza di ognuno di loro.
|
|
35
|
+
// Solo il pane task ha una cornice propria (`TASKS_PANE_CHROME`), perché porta
|
|
36
|
+
// la riga sort e le due righe meta.
|
|
37
|
+
//
|
|
38
|
+
// Il pane doc sta nello slot SINISTRO e paga comunque questa cornice, non quella
|
|
39
|
+
// del pane task: il costo lo decide ciò che il pane disegna, non il lato in cui
|
|
40
|
+
// sta. Se un giorno uno dei tre guadagnasse una riga fissa, questa costante deve
|
|
41
|
+
// sdoppiarsi e il budget guadagnare un ramo — una riga fissa non contata è ciò
|
|
42
|
+
// che fa sfondare `rows` e manda Ink nel ramo `clearTerminal`.
|
|
38
43
|
const SESSIONS_PANE_CHROME = 3; // 2 bordi + header "Sessions · …"
|
|
39
44
|
const PREVIEW_CHROME = 3; // marginTop + 2 bordi
|
|
40
45
|
/** Preview sessione: righe fisse = titolo + riga meta (size · turni · ultima
|
|
@@ -50,6 +55,11 @@ const MAX_SESSION_PREVIEW = 3;
|
|
|
50
55
|
* lettura di file a ogni movimento di selezione per un'anteprima che il detail
|
|
51
56
|
* dà per intero. */
|
|
52
57
|
const INBOX_DETAIL_FIXED = 2;
|
|
58
|
+
/** T160 — preview doc: nome intero del bersaglio + riga meta (cifre e nomi
|
|
59
|
+
* interi dei flag). Righe TUTTE fisse come per l'inbox, e per la stessa
|
|
60
|
+
* ragione: il corpo del file sta nel detail fullscreen, e mostrarne qui le
|
|
61
|
+
* prime righe costerebbe una lettura di file a ogni movimento di selezione. */
|
|
62
|
+
const DOC_DETAIL_FIXED = 2;
|
|
53
63
|
/** Altezza di ciascuna modale, marginTop incluso. In flusso, non in overlay:
|
|
54
64
|
* spingono giù i pane, quindi il loro costo va scalato dal budget. */
|
|
55
65
|
export const MODAL_HEIGHT = {
|
|
@@ -89,8 +99,13 @@ export const MODAL_HEIGHT = {
|
|
|
89
99
|
status: 0,
|
|
90
100
|
// T134 — il detail di un file inbox: sesta sostitutiva. Un file di nozioni
|
|
91
101
|
// arriva a 28KB, cioè più lungo di ogni task file del progetto: la stessa
|
|
92
|
-
// ragione delle altre cinque. Altezza da `
|
|
102
|
+
// ragione delle altre cinque. Altezza da `fileSheetCapacity`.
|
|
93
103
|
inbox: 0,
|
|
104
|
+
// T160 — il detail di un bersaglio doc, nona sostitutiva: stessa schermata
|
|
105
|
+
// dell'inbox (`useFileSheet`, due istanze) su un altro oggetto. Un modo
|
|
106
|
+
// proprio e non uno condiviso perché `esc` deve riportare al pane da cui si è
|
|
107
|
+
// entrati, e un modo solo obbligherebbe a ricordarselo altrove.
|
|
108
|
+
doc: 0,
|
|
94
109
|
// T134 — la lista hard-wrap: settima sostitutiva. Sul cappello lo scan trova
|
|
95
110
|
// 77 `WRAP` e 147 `misto`, cioè una lista che non entra in nessun box sopra i
|
|
96
111
|
// pane. Altezza da `wrapListCapacity`.
|
|
@@ -184,11 +199,13 @@ export function layoutBudget(input) {
|
|
|
184
199
|
previewCost = fixed + sessionFirstLines + sessionLastLines;
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
|
-
else if (input.preview === 'inbox') {
|
|
202
|
+
else if (input.preview === 'inbox' || input.preview === 'doc') {
|
|
188
203
|
// Tutte righe fisse: o il blocco entra intero o non esiste. Non c'è una
|
|
189
204
|
// parte variabile da sacrificare per farlo stare, quindi non c'è nemmeno la
|
|
190
|
-
// domanda di quanto concedergli.
|
|
191
|
-
|
|
205
|
+
// domanda di quanto concedergli. I due blocchi costano uguale e non sono
|
|
206
|
+
// mai a schermo insieme (stanno nei due pane dello stesso modo, e il blocco
|
|
207
|
+
// preview è uno solo, scelto dal focus).
|
|
208
|
+
const fixed = PREVIEW_CHROME + (input.preview === 'inbox' ? INBOX_DETAIL_FIXED : DOC_DETAIL_FIXED);
|
|
192
209
|
if (avail - floor - fixed >= 0) {
|
|
193
210
|
preview = true;
|
|
194
211
|
previewCost = fixed;
|
|
@@ -309,21 +326,27 @@ const STATUS_CHROME = 8;
|
|
|
309
326
|
export function statusCapacity(rows) {
|
|
310
327
|
return Math.max(0, (rows || 24) - SLACK - STATUS_CHROME);
|
|
311
328
|
}
|
|
312
|
-
// T134 — cornice del detail di un
|
|
313
|
-
//
|
|
314
|
-
//
|
|
329
|
+
// T134/T160 — cornice del detail di un FILE, condivisa dalle due istanze dello
|
|
330
|
+
// sheet (coda inbox e bersaglio doc). Gemella di STATUS_CHROME più una riga: il
|
|
331
|
+
// detail porta in fondo la riga dell'azione (quale comando partirà su `⏎`), che
|
|
332
|
+
// è l'unica cosa che l'utente deve leggere prima di premere.
|
|
315
333
|
// 2 bordi del box esterno
|
|
316
|
-
// 1 riga di testata (natura :: nome del
|
|
334
|
+
// 1 riga di testata (natura o tipo :: nome del bersaglio)
|
|
317
335
|
// 1 riga meta (marcatori · cifre · posizione nel testo)
|
|
318
336
|
// 1 riga hint
|
|
319
337
|
// 1 marginTop del box corpo
|
|
320
338
|
// 2 bordi del box corpo
|
|
321
339
|
// 1 marginTop della riga azione
|
|
322
340
|
// 1 riga azione
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
341
|
+
//
|
|
342
|
+
// Una costante sola per le due schermate perché è la stessa cornice, disegnata
|
|
343
|
+
// dallo stesso schema: due numeri uguali con due nomi diversi divergerebbero al
|
|
344
|
+
// primo ritocco di una delle due, e quella che resta indietro sfonda `rows`
|
|
345
|
+
// senza che niente lo dica.
|
|
346
|
+
const FILE_SHEET_CHROME = 10;
|
|
347
|
+
/** Righe di testo che entrano nel terminale dentro uno sheet di file. */
|
|
348
|
+
export function fileSheetCapacity(rows) {
|
|
349
|
+
return Math.max(0, (rows || 24) - SLACK - FILE_SHEET_CHROME);
|
|
327
350
|
}
|
|
328
351
|
// T134 — cornice della lista hard-wrap.
|
|
329
352
|
// 2 bordi del box esterno
|
package/package.json
CHANGED
package/scripts/deck-run
CHANGED
|
@@ -299,10 +299,11 @@ WORKDIR="${LOOM_DECK_WORKDIR:-$PWD}"
|
|
|
299
299
|
# conversazione portava due titoli diversi, senza errore. Le quadre che
|
|
300
300
|
# delimitavano il prefisso sono cadute col prefisso stesso: l'emoji da sola si
|
|
301
301
|
# stacca già dallo slug.
|
|
302
|
-
# T161 — le
|
|
303
|
-
# (`SPAWN_ACTIONS` in `src/spawn-catalog.ts`), che il progetto può
|
|
304
|
-
# quattro nominano un'azione su una task (📐🚀📊🏁), 🧹 il file inbox
|
|
305
|
-
# drena, 📏 il path che si srotola
|
|
302
|
+
# T161/T160 — le sette stanno tutte nei TEMPLATE di titolo del catalogo delle
|
|
303
|
+
# azioni (`SPAWN_ACTIONS` in `src/spawn-catalog.ts`), che il progetto può
|
|
304
|
+
# riscrivere: quattro nominano un'azione su una task (📐🚀📊🏁), 🧹 il file inbox
|
|
305
|
+
# che si drena, 📏 il path che si srotola, 🧭 il bersaglio doc che si
|
|
306
|
+
# riorganizza.
|
|
306
307
|
# Questa whitelist ha un GEMELLO TypeScript (`saneNote` in `src/sane-note.ts`),
|
|
307
308
|
# che riduce il template al salvataggio perché quello che sta nel file di
|
|
308
309
|
# progetto sia quello che la tab mostrerà. I due lati non si leggono a vicenda e
|
|
@@ -335,7 +336,7 @@ _sane_note() { # <raw> → nota ridotta, spazi collassati, cap 60 char
|
|
|
335
336
|
# `tr` non ne ha bisogno: squeeza uno spazio ASCII, e 0x20 non compare mai
|
|
336
337
|
# come byte di continuazione di una sequenza UTF-8.
|
|
337
338
|
s="$(printf '%s' "$1" \
|
|
338
|
-
| LC_ALL=C.UTF-8 sed 's/[^A-Za-z0-9 _
|
|
339
|
+
| LC_ALL=C.UTF-8 sed 's/[^A-Za-z0-9 _àèéìòùÀÈÉÌÒÙ📐🚀📊🏁🧹📏🧭-]//g' \
|
|
339
340
|
| tr -s ' ')"
|
|
340
341
|
s="${s# }"; s="${s% }"
|
|
341
342
|
printf '%s' "${s:0:60}"
|