@lamemind/loom-deck 0.69.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 +55 -19
- 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/session-meta.js +45 -27
- 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/panes.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
// I due pane
|
|
2
|
+
// I due pane del modo TASK — lista task e conversazioni — con i rispettivi
|
|
3
3
|
// header. Sono presentazionali puri: ricevono la vista già selezionata e le
|
|
4
4
|
// larghezze già calcolate, non li derivano.
|
|
5
|
+
//
|
|
6
|
+
// T160 · D1 — i pane dell'altro modo sono usciti di qui: la coda inbox in
|
|
7
|
+
// `ui/inbox-pane.tsx`, l'albero doc in `ui/doc-pane.tsx`, e ciò che tutti e
|
|
8
|
+
// quattro condividono (`HeaderLine`) in `ui/header-line.tsx`. L'asse dello split
|
|
9
|
+
// è per PANE e non per livello: ogni pane porta il proprio header, la propria
|
|
10
|
+
// riga e la propria nota di vuoto, e sono le tre cose che si toccano insieme.
|
|
11
|
+
// Nessuno dei tre moduli estratti importa questo file — la dipendenza va in un
|
|
12
|
+
// verso solo, ed è verificabile a grep (`coding-standards` §Come si splitta un
|
|
13
|
+
// file ricresciuto).
|
|
5
14
|
import { Box, Text } from 'ink';
|
|
6
15
|
import { cut, pad, sanitize, termWidth } from '../width.js';
|
|
7
16
|
import { paneTextWidth } from '../layout.js';
|
|
8
|
-
import { CARET, CARET_OFF,
|
|
9
|
-
import { NATURA_SHORT, NATURA_W, inboxMark } from '../inbox.js';
|
|
10
|
-
import { inboxView, } from '../inbox-views.js';
|
|
17
|
+
import { CARET, CARET_OFF, LIVE_BUSY, LIVE_IDLE, LIVE_NONE, MODEL_W, SID_CHARS, TASK_EMPTY, WARN, metaCount, modelShort, relTime, } from '../glyphs.js';
|
|
11
18
|
import { TaskRow } from './task-row.js';
|
|
19
|
+
import { HeaderLine } from './header-line.js';
|
|
12
20
|
import { META_ROWS, ROW_ALL, ROW_SPOT } from '../model.js';
|
|
13
21
|
import { rowLabel, sessionTitle } from '../session-list.js';
|
|
14
22
|
import { sessionView, taskView, } from '../pane-views.js';
|
|
15
|
-
import {
|
|
23
|
+
import { sessionHeaderParts, taskHeaderParts } from '../pane-header.js';
|
|
16
24
|
import { describeSort, PRI_ENTRIES, PROG_ENTRIES, } from '../view.js';
|
|
17
25
|
/**
|
|
18
26
|
* Header del pane task, tagliato QUI e non da Ink — stesso motivo del gemello
|
|
@@ -39,12 +47,6 @@ export function TasksHeader({ counts, active, above, below, focused, columns, })
|
|
|
39
47
|
const { parts, shown } = taskHeaderParts(counts, active, above, below, columns);
|
|
40
48
|
return _jsx(HeaderLine, { parts: parts, shown: shown, focused: focused });
|
|
41
49
|
}
|
|
42
|
-
/** Resa di un header già tagliato: ogni parte col suo stile, l'attiva in
|
|
43
|
-
* video inverso (T100/D5 — costa 0 colonne e non entra in gara con la
|
|
44
|
-
* semantica di colore già occupata). */
|
|
45
|
-
function HeaderLine({ parts, shown, focused, }) {
|
|
46
|
-
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) }));
|
|
47
|
-
}
|
|
48
50
|
export function TasksPane({ tasks, counts, activeView, paneCount, view, selected, spotCount, allCount, idW, tailW, focused, loadError, windowStart, above, below, columns, data, blockMark, }) {
|
|
49
51
|
const allSelected = selected === ROW_ALL;
|
|
50
52
|
const spotSelected = selected === ROW_SPOT;
|
|
@@ -85,69 +87,6 @@ export function SessionsHeader({ parentLabel, counts, active, above, below, focu
|
|
|
85
87
|
const { parts, shown } = sessionHeaderParts(parentLabel, counts, active, above, below, columns);
|
|
86
88
|
return _jsx(HeaderLine, { parts: parts, shown: shown, focused: focused });
|
|
87
89
|
}
|
|
88
|
-
/**
|
|
89
|
-
* T134 — header del pane inbox, terzo gemello dei due sopra: le parti e il loro
|
|
90
|
-
* taglio vengono da `pane-header.ts`, lo stesso modulo da cui `frame.ts` ricava
|
|
91
|
-
* le colonne cliccabili.
|
|
92
|
-
*/
|
|
93
|
-
export function InboxHeader({ counts, active, above, below, focused, columns, }) {
|
|
94
|
-
const { parts, shown } = inboxHeaderParts(counts, active, above, below, columns);
|
|
95
|
-
return _jsx(HeaderLine, { parts: parts, shown: shown, focused: focused });
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* T134 — il pane inbox, alternativo a quello delle sessioni nello slot destro
|
|
99
|
-
* (D6). Uno dei due è montato e l'altro non esiste: `^B` li scambia.
|
|
100
|
-
*
|
|
101
|
-
* Mostra SEMPRE TUTTO, senza filtro sulla task selezionata a sinistra (D7
|
|
102
|
-
* preflight, che supersede la clausola di D6 congelate): niente righe meta,
|
|
103
|
-
* nessun trattamento speciale per il cappello vuoto o per un cappello che
|
|
104
|
-
* nomina una task non più in `tasks.md`. Ne discende che la colonna `CAPPELLO`
|
|
105
|
-
* di `doc-metrics` non serve al filtro — resta nel blocco preview, dove dice
|
|
106
|
-
* qualcosa senza costare una colonna su ogni riga.
|
|
107
|
-
*
|
|
108
|
-
* La riga porta marcatori, branch e nome (D8). Non le cifre (nozioni, aperte,
|
|
109
|
-
* char): stanno nella preview, e su un pane largo la metà del terminale ogni
|
|
110
|
-
* colonna fissa la paga la cella elastica del nome — che è l'unica cosa con cui
|
|
111
|
-
* si riconosce un file.
|
|
112
|
-
*/
|
|
113
|
-
export function InboxPane({ files, counts, activeView, paneCount, selectedPath, focused, above, below, columns, ok, scanned, }) {
|
|
114
|
-
const width = paneTextWidth(columns);
|
|
115
|
-
// La cella elastica si calcola per SOTTRAZIONE dalle colonne fisse, come il
|
|
116
|
-
// titolo della lista sessioni: pavimento 0 e non un minimo di cortesia — è un
|
|
117
|
-
// tetto, non una preferenza, e alzarlo sopra lo spazio reale fa uscire la riga
|
|
118
|
-
// dal pane mangiandone il bordo (invariante ③ di width.ts).
|
|
119
|
-
const ageW = 3;
|
|
120
|
-
const nameW = Math.max(0, width -
|
|
121
|
-
(2 /* caret */ +
|
|
122
|
-
NATURA_W +
|
|
123
|
-
1 /* gutter */ +
|
|
124
|
-
INBOX_MARK_W +
|
|
125
|
-
1 /* gutter */ +
|
|
126
|
-
1 /* gutter prima della data */ +
|
|
127
|
-
ageW));
|
|
128
|
-
return (_jsxs(Box, { flexDirection: "column", width: "50%", borderStyle: "single", borderColor: focused ? 'cyan' : 'gray', paddingX: 1, children: [_jsx(InboxHeader, { counts: counts, active: activeView, above: above, below: below, focused: focused, columns: columns }), paneCount === 0 ? (
|
|
129
|
-
// Tre vuoti diversi, e dirlo è il punto: «non ho ancora misurato» non è
|
|
130
|
-
// «ho misurato e non c'è niente», e «la misura si è rotta» non è nessuno
|
|
131
|
-
// dei due (D2 preflight). Una lista vuota che non dice quale dei tre è
|
|
132
|
-
// si legge come un pane rotto.
|
|
133
|
-
_jsx(Text, { color: "yellow", wrap: "truncate-end", children: cut(!scanned
|
|
134
|
-
? 'coda non ancora misurata'
|
|
135
|
-
: !ok
|
|
136
|
-
? 'misura della coda fallita: plugin assente o script in errore'
|
|
137
|
-
: inboxView(activeView).empty, width) })) : (files.map((f) => {
|
|
138
|
-
const sel = f.path === selectedPath;
|
|
139
|
-
const mark = inboxMark(f);
|
|
140
|
-
// Il branch sta DENTRO la cella del nome, non in una colonna propria:
|
|
141
|
-
// è raro, e una colonna dedicata costerebbe spazio vuoto su ogni riga
|
|
142
|
-
// non-branchata di un pane largo la metà del terminale. Stessa scelta
|
|
143
|
-
// del marcatore di fork nella lista sessioni.
|
|
144
|
-
const branchMark = f.branch ? `⟨${f.branch}⟩ ` : '';
|
|
145
|
-
const inner = Math.max(0, nameW - termWidth(sanitize(branchMark)));
|
|
146
|
-
const name = cut(f.basename.replace(/\.md$/, ''), inner);
|
|
147
|
-
const age = relTime(f.created * 1000);
|
|
148
|
-
return (_jsxs(Text, { inverse: sel && focused, bold: sel && !focused, wrap: "truncate-end", children: [sel ? CARET : CARET_OFF, _jsx(Text, { color: mark === 'broken' ? 'red' : mark === 'queued' ? 'green' : undefined, dimColor: mark === 'held' || mark === 'branched', children: pad(NATURA_SHORT[f.natura], NATURA_W) }), ' ', _jsx(Text, { color: mark === 'branched' ? 'yellow' : undefined, children: pad(INBOX_MARK[mark], INBOX_MARK_W) }), ' ', branchMark ? _jsx(Text, { color: "yellow", children: sanitize(branchMark) }) : null, _jsx(Text, { dimColor: mark !== 'queued', children: name }), ' '.repeat(Math.max(0, inner - termWidth(name))), ' ', _jsx(Text, { dimColor: true, children: pad(age, ageW, 'right') })] }, f.path));
|
|
149
|
-
}))] }));
|
|
150
|
-
}
|
|
151
90
|
export function SessionsPane({ parentLabel, isSpot, isAll, bindings, taskW, ageW, rows, counts, activeView, paneCount, selectedId, focused, above, below, columns, forkOf, sessionNotes, sessionPriority, projectCore, live, }) {
|
|
152
91
|
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 ? (
|
|
153
92
|
// T100 — la nota della vista di default resta quella storica, che nomina
|
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}"
|