@lamemind/loom-deck 0.65.2 → 0.66.1
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 +39 -2
- package/dist/cli.js +1 -1
- package/dist/deck-model.js +2 -1
- package/dist/frame.js +4 -1
- package/dist/hooks.js +16 -6
- package/dist/input.js +6 -0
- package/dist/live-sessions.js +15 -0
- package/dist/overlays/sheet.js +35 -8
- package/dist/task-index.js +25 -2
- package/dist/ui/detail-screen.js +4 -4
- package/dist/ui/panes.js +14 -3
- package/dist/ui/screens.js +1 -1
- package/dist/viewport.js +6 -5
- package/package.json +1 -1
package/dist/actions.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { useMemo, useRef } from 'react';
|
|
19
19
|
import { randomUUID } from 'node:crypto';
|
|
20
20
|
import { loadPromptCatalog, modelFor } from './prompt-catalog.js';
|
|
21
|
-
import { appendNote, appendPin, appendSessionRecord, appendTaskBinding, } from './task-index.js';
|
|
21
|
+
import { appendNote, appendPin, appendPriority, appendSessionRecord, appendTaskBinding, } from './task-index.js';
|
|
22
22
|
import { neighborId } from './session-list.js';
|
|
23
23
|
import { cut, cutMiddle } from './width.js';
|
|
24
24
|
import { saveView, viewFilePath } from './view-store.js';
|
|
@@ -114,12 +114,21 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
|
|
|
114
114
|
// nomi diversi per la stessa conversazione. Vale anche sugli acceleratori
|
|
115
115
|
// della lista (^K/^P/^R), che passano di qui con `spawnNote` sul proprio
|
|
116
116
|
// default '' (P1 preflight) — nascono quindi titolati anche loro.
|
|
117
|
-
function spawnForTask(id, kind, modelKind, spawnNote = '', prompt) {
|
|
117
|
+
function spawnForTask(id, kind, modelKind, spawnNote = '', prompt, priority = false) {
|
|
118
118
|
const sid = randomUUID();
|
|
119
119
|
const note = spawnNote || fallbackTitle(tasksDir, id, kind) || '';
|
|
120
120
|
appendTaskBinding(cwd, sid, id);
|
|
121
121
|
if (note)
|
|
122
122
|
appendNote(cwd, sid, note);
|
|
123
|
+
// T158 — la marca si appende NELLO STESSO momento del binding, prima che la
|
|
124
|
+
// tab esista: il `sessionId` è già noto (lo pinna `deck-run`), quindi il primo
|
|
125
|
+
// cambio di stato della conversazione trova la marca già su disco. Scriverla
|
|
126
|
+
// dopo lo spawn sarebbe una corsa contro il primo `Stop`.
|
|
127
|
+
//
|
|
128
|
+
// Solo il ramo acceso scrive: un `priority:false` allo spawn di ogni sessione
|
|
129
|
+
// riempirebbe il sidecar di smarcature di conversazioni mai marcate.
|
|
130
|
+
if (priority)
|
|
131
|
+
appendPriority(cwd, sid, true);
|
|
123
132
|
const spawned = spawnDeck(id, cwd, sid, kind, modelKind, note, prompt);
|
|
124
133
|
spawned.child.on('error', () => setNote(`⚠ spawn ${id} fallito (${DECK_RUN})`));
|
|
125
134
|
// Il modello resta SEMPRE visibile anche quando è il default, perché è un
|
|
@@ -279,6 +288,33 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
|
|
|
279
288
|
model.setSelSessionId(landing);
|
|
280
289
|
setNote(`${isPinned ? 'unpin' : '📌 pin'} ${sid.slice(0, 8)}`);
|
|
281
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* T158 — `a`: marca/smarca la conversazione selezionata come PRIORITARIA.
|
|
293
|
+
*
|
|
294
|
+
* Gemella di `togglePin` nella forma (azione immediata, scrive il sidecar,
|
|
295
|
+
* ricarica subito) ma non nell'effetto: la marca non sposta e non filtra
|
|
296
|
+
* niente, quindi non c'è nessun vicino su cui atterrare — la riga resta dov'è
|
|
297
|
+
* e cambia solo il glifo.
|
|
298
|
+
*
|
|
299
|
+
* Vale anche su una pinnata STALE: il transcript non c'è più, ma la marca è un
|
|
300
|
+
* attributo del `sessionId` e non del transcript, e l'unico posto da cui
|
|
301
|
+
* toglierla resta quella riga.
|
|
302
|
+
*/
|
|
303
|
+
function togglePriority() {
|
|
304
|
+
if (model.focus !== 'sessions') {
|
|
305
|
+
setNote('a → priorità: seleziona una sessione (→ per il pane)');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const sid = model.selSessionId;
|
|
309
|
+
if (!sid) {
|
|
310
|
+
setNote('a → nessuna sessione da marcare');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const on = model.sessionPriority.has(sid);
|
|
314
|
+
appendPriority(cwd, sid, !on);
|
|
315
|
+
model.reloadSessions();
|
|
316
|
+
setNote(`${on ? 'priorità via' : '🚨 prioritaria'} ${sid.slice(0, 8)}`);
|
|
317
|
+
}
|
|
282
318
|
/** `t` — terminale a project root, con un titolo che il matcher di compass riconosce. */
|
|
283
319
|
function openTerminal() {
|
|
284
320
|
const title = model.identity ? `🖥️ ${model.identity.name} [term]` : null;
|
|
@@ -365,6 +401,7 @@ export function useDeckActions({ cwd, tasksPath, tasksDir, columns, model, setNo
|
|
|
365
401
|
assignSession,
|
|
366
402
|
forkSession,
|
|
367
403
|
togglePin,
|
|
404
|
+
togglePriority,
|
|
368
405
|
drainInbox,
|
|
369
406
|
unwrapPath,
|
|
370
407
|
openTerminal,
|
package/dist/cli.js
CHANGED
|
@@ -270,7 +270,7 @@ function Deck({ cwd, tasksPath, tasksDir }) {
|
|
|
270
270
|
inboxPane: model.rightPane === 'inbox',
|
|
271
271
|
hasInbox: model.selInbox !== null,
|
|
272
272
|
gitlink: gitlink.enabled,
|
|
273
|
-
}), indicators: indicators, columns: columns }), frame.launchLine ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [launch.segments.map((s) => s.text).join(LAUNCH_SEP), launch.overflow > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 +", launch.overflow, " fuori riga"] })) : null, launch.unreachable > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 ", launch.unreachable, " oltre la 9\u00AA (non raggiungibili)"] })) : null, launch.filler, BARE_LABEL.text, ' ', bareButtons.nodes] })) : null, mode === 'create' ? _jsx(TextBox, { glyph: "C", value: textModals.draft }) : null, mode === 'note' ? _jsx(TextBox, { glyph: "\u270E", value: textModals.noteDraft }) : null, mode === 'sort' ? _jsx(SortModal, { sort: model.view.sort }) : null, mode === 'filter' ? (_jsx(FilterModal, { view: model.view, cursor: viewModals.filterCursor })) : null, mode === 'edit' && textModals.edit && textModals.editTask ? (_jsx(EditModal, { id: textModals.editTask.id, draft: textModals.edit, row: textModals.editCursor.row, caret: textModals.editCursor.caret, columns: columns })) : null, mode === 'purge' && purge.draft ? (_jsx(PurgeModal, { draft: purge.draft, columns: columns })) : null, _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(TasksPane, { tasks: frame.windowTasks, counts: model.taskCounts, activeView: model.taskViewId, paneCount: model.paneTasks.length, view: model.view, selected: model.selIndex, spotCount: model.spotCount, allCount: model.sessions.length, idW: model.taskCols.id, tailW: model.taskCols.tail, focused: model.focus === 'tasks', loadError: model.loadError, windowStart: frame.taskWin.start, above: frame.taskWin.start, below: model.paneTasks.length - frame.taskWin.end, columns: columns, data: model.taskRowData, blockMark: model.blockMark }), model.rightPane === 'inbox' ? (_jsx(InboxPane, { files: frame.windowInbox, counts: model.inboxCounts, activeView: model.inboxViewId, paneCount: model.inboxFiles.length, selectedPath: model.selInboxPath, focused: model.focus === 'inbox', above: frame.inboxWin.start, below: model.inboxFiles.length - frame.inboxWin.end, columns: columns, ok: model.inboxOk, scanned: model.inboxScanned })) : (_jsx(SessionsPane, { parentLabel: model.parentLabel, isSpot: model.isSpot, isAll: model.isAll, bindings: model.bindings, taskW: model.sessionCols.task, ageW: model.sessionCols.age, rows: frame.windowRows, counts: model.sessionCounts, activeView: model.sessionViewId, paneCount: model.sessionRows.length, selectedId: model.selSessionId ?? undefined, focused: model.focus === 'sessions', above: frame.sessionWin.start, below: model.sessionRows.length - frame.sessionWin.end, columns: columns, forkOf: model.forkOf, sessionNotes: model.sessionNotes, projectCore: model.projectCore, live: model.live }))] }), frame.budget.preview && previewKind === 'task' && model.detail ? (_jsx(PreviewPane, { kind: "task", detail: model.detail, maxLines: frame.budget.detailLines, columns: columns })) : frame.budget.preview && previewKind === 'inbox' && model.selInbox ? (_jsx(PreviewPane, { kind: "inbox", file: model.selInbox, columns: columns })) : frame.budget.preview && previewKind === 'session' && model.selSessionObj ? (_jsx(PreviewPane, { kind: "session", s: model.selSessionObj, firstLines: frame.budget.sessionFirstLines, lastLines: frame.budget.sessionLastLines, columns: columns, origin: model.forkOf.get(model.selSessionObj.sessionId) ?? null, note: model.sessionNotes.get(model.selSessionObj.sessionId) ?? '', live: model.live.get(model.selSessionObj.sessionId) ?? null, resumeModel: model.resumeModel })) : null, note ? _jsx(Text, { color: "green", wrap: "truncate-end", children: sanitize(note) }) : null] }));
|
|
273
|
+
}), indicators: indicators, columns: columns }), frame.launchLine ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [launch.segments.map((s) => s.text).join(LAUNCH_SEP), launch.overflow > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 +", launch.overflow, " fuori riga"] })) : null, launch.unreachable > 0 ? (_jsxs(Text, { color: "yellow", children: [" \u00B7 ", launch.unreachable, " oltre la 9\u00AA (non raggiungibili)"] })) : null, launch.filler, BARE_LABEL.text, ' ', bareButtons.nodes] })) : null, mode === 'create' ? _jsx(TextBox, { glyph: "C", value: textModals.draft }) : null, mode === 'note' ? _jsx(TextBox, { glyph: "\u270E", value: textModals.noteDraft }) : null, mode === 'sort' ? _jsx(SortModal, { sort: model.view.sort }) : null, mode === 'filter' ? (_jsx(FilterModal, { view: model.view, cursor: viewModals.filterCursor })) : null, mode === 'edit' && textModals.edit && textModals.editTask ? (_jsx(EditModal, { id: textModals.editTask.id, draft: textModals.edit, row: textModals.editCursor.row, caret: textModals.editCursor.caret, columns: columns })) : null, mode === 'purge' && purge.draft ? (_jsx(PurgeModal, { draft: purge.draft, columns: columns })) : null, _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(TasksPane, { tasks: frame.windowTasks, counts: model.taskCounts, activeView: model.taskViewId, paneCount: model.paneTasks.length, view: model.view, selected: model.selIndex, spotCount: model.spotCount, allCount: model.sessions.length, idW: model.taskCols.id, tailW: model.taskCols.tail, focused: model.focus === 'tasks', loadError: model.loadError, windowStart: frame.taskWin.start, above: frame.taskWin.start, below: model.paneTasks.length - frame.taskWin.end, columns: columns, data: model.taskRowData, blockMark: model.blockMark }), model.rightPane === 'inbox' ? (_jsx(InboxPane, { files: frame.windowInbox, counts: model.inboxCounts, activeView: model.inboxViewId, paneCount: model.inboxFiles.length, selectedPath: model.selInboxPath, focused: model.focus === 'inbox', above: frame.inboxWin.start, below: model.inboxFiles.length - frame.inboxWin.end, columns: columns, ok: model.inboxOk, scanned: model.inboxScanned })) : (_jsx(SessionsPane, { parentLabel: model.parentLabel, isSpot: model.isSpot, isAll: model.isAll, bindings: model.bindings, taskW: model.sessionCols.task, ageW: model.sessionCols.age, rows: frame.windowRows, counts: model.sessionCounts, activeView: model.sessionViewId, paneCount: model.sessionRows.length, selectedId: model.selSessionId ?? undefined, focused: model.focus === 'sessions', above: frame.sessionWin.start, below: model.sessionRows.length - frame.sessionWin.end, columns: columns, forkOf: model.forkOf, sessionNotes: model.sessionNotes, sessionPriority: model.sessionPriority, projectCore: model.projectCore, live: model.live }))] }), frame.budget.preview && previewKind === 'task' && model.detail ? (_jsx(PreviewPane, { kind: "task", detail: model.detail, maxLines: frame.budget.detailLines, columns: columns })) : frame.budget.preview && previewKind === 'inbox' && model.selInbox ? (_jsx(PreviewPane, { kind: "inbox", file: model.selInbox, columns: columns })) : frame.budget.preview && previewKind === 'session' && model.selSessionObj ? (_jsx(PreviewPane, { kind: "session", s: model.selSessionObj, firstLines: frame.budget.sessionFirstLines, lastLines: frame.budget.sessionLastLines, columns: columns, origin: model.forkOf.get(model.selSessionObj.sessionId) ?? null, note: model.sessionNotes.get(model.selSessionObj.sessionId) ?? '', live: model.live.get(model.selSessionObj.sessionId) ?? null, resumeModel: model.resumeModel })) : null, note ? _jsx(Text, { color: "green", wrap: "truncate-end", children: sanitize(note) }) : null] }));
|
|
274
274
|
}
|
|
275
275
|
const cwd = process.cwd();
|
|
276
276
|
// T116 — `exitOnCtrlC: false` toglie a Ink l'uscita immediata su `^C`: senza,
|
package/dist/deck-model.js
CHANGED
|
@@ -111,7 +111,7 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, setNote, }) {
|
|
|
111
111
|
// di STATO in fondo al frame (il feedback di un'azione). Due concetti diversi
|
|
112
112
|
// a una lettera di distanza sarebbero una trappola di lettura — e di
|
|
113
113
|
// scrittura, visto che `setNote` compare in quasi ogni ramo.
|
|
114
|
-
const { sessions, bindings, forkOf, pinned, notes: sessionNotes, live, reload: reloadSessions, } = useSessions(cwd);
|
|
114
|
+
const { sessions, bindings, forkOf, pinned, notes: sessionNotes, priority: sessionPriority, live, reload: reloadSessions, } = useSessions(cwd);
|
|
115
115
|
const [focus, setFocus] = useState('tasks');
|
|
116
116
|
// T39 — selezione KEYED SU ID, non su indice. Con una vista trasformata
|
|
117
117
|
// (filtro/sort) l'indice non identifica più la stessa task: leggere l'array
|
|
@@ -470,6 +470,7 @@ export function useDeckModel({ cwd, tasksPath, tasksDir, setNote, }) {
|
|
|
470
470
|
forkOf,
|
|
471
471
|
pinned,
|
|
472
472
|
sessionNotes,
|
|
473
|
+
sessionPriority,
|
|
473
474
|
live,
|
|
474
475
|
reloadSessions,
|
|
475
476
|
detail,
|
package/dist/frame.js
CHANGED
|
@@ -100,7 +100,10 @@ export function deckLegend(state) {
|
|
|
100
100
|
// allo spawn, e due nomi per una cosa sola li paga chi legge le due
|
|
101
101
|
// schermate. Rename di sola ETICHETTA: il dato resta `note` nel sidecar e
|
|
102
102
|
// `--title-note` in deck-run, dove rinominarlo sarebbe un breaking.
|
|
103
|
-
|
|
103
|
+
// T158 — `a` sta accanto a `p` perché condivide la condizione (`canPin`:
|
|
104
|
+
// focus sul pane e una riga selezionata, stale compresa) e la forma: due
|
|
105
|
+
// attributi della stessa conversazione, scritti nello stesso sidecar.
|
|
106
|
+
...(canPin ? ['p pin', 'a priorità', 'N titolo', 'A assegna'] : []),
|
|
104
107
|
// T121 — le due voci nominano il GESTO e non l'oggetto: «status» da solo
|
|
105
108
|
// sarebbe indistinguibile dal recap della task su `^K`, che è un'altra
|
|
106
109
|
// cosa e sta a due voci di distanza.
|
package/dist/hooks.js
CHANGED
|
@@ -143,6 +143,7 @@ export function useSessions(projectRoot) {
|
|
|
143
143
|
forkOf: new Map(),
|
|
144
144
|
pinned: new Map(),
|
|
145
145
|
notes: new Map(),
|
|
146
|
+
priority: new Set(),
|
|
146
147
|
live: new Map(),
|
|
147
148
|
});
|
|
148
149
|
// T50 — pin/unpin scrive il sidecar e vuole feedback IMMEDIATO, non al
|
|
@@ -160,7 +161,13 @@ export function useSessions(projectRoot) {
|
|
|
160
161
|
}
|
|
161
162
|
catch {
|
|
162
163
|
sessions = [];
|
|
163
|
-
index = {
|
|
164
|
+
index = {
|
|
165
|
+
bindings: new Map(),
|
|
166
|
+
forkOf: new Map(),
|
|
167
|
+
pinned: new Map(),
|
|
168
|
+
notes: new Map(),
|
|
169
|
+
priority: new Set(),
|
|
170
|
+
};
|
|
164
171
|
}
|
|
165
172
|
// T62 — le vive stanno sullo STESSO tick delle altre fonti, non su una
|
|
166
173
|
// scala propria come `useArchivable`: `status` cambia a ogni turno, quindi
|
|
@@ -174,10 +181,11 @@ export function useSessions(projectRoot) {
|
|
|
174
181
|
catch {
|
|
175
182
|
live = new Map();
|
|
176
183
|
}
|
|
177
|
-
const { bindings, forkOf, pinned, notes } = index;
|
|
178
|
-
// La signature copre anche fork, pin
|
|
179
|
-
// toggle di pin
|
|
180
|
-
// quindi devono forzare il re-render come
|
|
184
|
+
const { bindings, forkOf, pinned, notes, priority } = index;
|
|
185
|
+
// La signature copre anche fork, pin, note e marca di priorità: un record
|
|
186
|
+
// di lineage, un toggle di pin, una nota appena scritta o un 🚨 acceso
|
|
187
|
+
// cambiano la lista renderizzata, quindi devono forzare il re-render come
|
|
188
|
+
// farebbe un binding nuovo.
|
|
181
189
|
const sig = sessions.map((s) => `${s.sessionId}:${s.ts}`).join('|') +
|
|
182
190
|
'#' +
|
|
183
191
|
[...bindings.entries()].map(([k, v]) => `${k}=${v}`).sort().join(',') +
|
|
@@ -188,11 +196,13 @@ export function useSessions(projectRoot) {
|
|
|
188
196
|
'#' +
|
|
189
197
|
[...notes.entries()].map(([k, v]) => `${k}"${v}`).sort().join(',') +
|
|
190
198
|
'#' +
|
|
199
|
+
[...priority].sort().join(',') +
|
|
200
|
+
'#' +
|
|
191
201
|
liveSig(live);
|
|
192
202
|
if (sig === lastSig)
|
|
193
203
|
return;
|
|
194
204
|
lastSig = sig;
|
|
195
|
-
setState({ sessions, bindings, forkOf, pinned, notes, live });
|
|
205
|
+
setState({ sessions, bindings, forkOf, pinned, notes, priority, live });
|
|
196
206
|
};
|
|
197
207
|
reloadRef.current = reload;
|
|
198
208
|
reload();
|
package/dist/input.js
CHANGED
|
@@ -479,6 +479,12 @@ export function useDeckInput({ cwd, tasksDir, mode, setMode, setNote, model, act
|
|
|
479
479
|
// selezionata del pane sessioni.
|
|
480
480
|
actions.togglePin();
|
|
481
481
|
}
|
|
482
|
+
else if (input === 'a') {
|
|
483
|
+
// T158 — marca prioritaria, gemella di `p`. Minuscola perché è
|
|
484
|
+
// un'azione immediata, e `a` per «allarme» (il glifo di riga è 🚨): la
|
|
485
|
+
// `A` maiuscola resta al modale di assegnazione, come `f`/`F` e `c`/`C`.
|
|
486
|
+
actions.togglePriority();
|
|
487
|
+
}
|
|
482
488
|
else if (input === 'm') {
|
|
483
489
|
// T148/T154 — bersaglio deciso dal focus E dalla selezione (P2): con
|
|
484
490
|
// una riga sessioni selezionata (stale compresa, P3) scorre il modello
|
package/dist/live-sessions.js
CHANGED
|
@@ -67,6 +67,21 @@ export function parseLiveEntry(content, pid) {
|
|
|
67
67
|
// il guard esiste per escludere), `cwd` è il filtro di progetto.
|
|
68
68
|
if (!sessionId || !procStart || !cwd)
|
|
69
69
|
return null;
|
|
70
|
+
// Il registry tiene un file anche per i processi che NON sono una
|
|
71
|
+
// conversazione dell'utente: subagent SDK, fork parkeggiati in background,
|
|
72
|
+
// spare pty host. Hanno lo stesso `cwd` del progetto e un pid davvero vivo —
|
|
73
|
+
// quindi `isAlive` non li scarta — ma nessuna tab a cui tornare: elencarli
|
|
74
|
+
// conta più conversazioni di quante ne siano aperte, e `--resume` su una di
|
|
75
|
+
// esse non riporta l'utente da nessuna parte.
|
|
76
|
+
//
|
|
77
|
+
// Servono DUE campi perché uno solo non discrimina: un job in background di
|
|
78
|
+
// Claude Code scrive `entrypoint:"cli"` come una sessione vera e si distingue
|
|
79
|
+
// solo per `kind:"bg"`. Entrambi assenti valgono come sessione interattiva —
|
|
80
|
+
// una versione del CLI che non li scrivesse non deve svuotare la lista.
|
|
81
|
+
if ((typeof d.entrypoint === 'string' ? d.entrypoint : 'cli') !== 'cli')
|
|
82
|
+
return null;
|
|
83
|
+
if ((typeof d.kind === 'string' ? d.kind : 'interactive') !== 'interactive')
|
|
84
|
+
return null;
|
|
70
85
|
return {
|
|
71
86
|
entry: {
|
|
72
87
|
sessionId,
|
package/dist/overlays/sheet.js
CHANGED
|
@@ -22,16 +22,23 @@ import { ACTION_HOTKEYS, DETAIL_ACTIONS, MODELS, MODEL_DEFAULT, specializeRecap,
|
|
|
22
22
|
import { taskIsEpic } from '../tasks.js';
|
|
23
23
|
import { fieldsKey } from '../fields.js';
|
|
24
24
|
import { loadPromptCatalog, modelFor, promptFor } from '../prompt-catalog.js';
|
|
25
|
-
// T117 — le
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
25
|
+
// T117 — le righe dell'area di compilazione del detail, nell'ordine in cui si
|
|
26
|
+
// leggono dall'alto. Gli indici sono nominati perché li usano insieme l'handler
|
|
27
|
+
// dei tasti e la resa, e un `2` nudo in due file diversi è la coordinata che
|
|
28
|
+
// scade appena una riga si sposta.
|
|
29
|
+
//
|
|
30
|
+
// T158 — `priority` sta in coda e non accanto al titolo: le prime quattro righe
|
|
31
|
+
// sono i parametri del COMANDO che parte, questa è un attributo della
|
|
32
|
+
// conversazione che nasce, e vive nel sidecar invece che nell'argv di deck-run.
|
|
33
|
+
export const DROW = { action: 0, prompt: 1, model: 2, title: 3, priority: 4 };
|
|
34
|
+
/** T158 — le due voci della riga priorità, nell'ordine degli indici. */
|
|
35
|
+
export const PRIORITY_CHOICES = ['no', '🚨 sì'];
|
|
30
36
|
export const DETAIL_FIELDS = [
|
|
31
37
|
{ kind: 'choice', count: DETAIL_ACTIONS.length, hotkeys: ACTION_HOTKEYS },
|
|
32
38
|
{ kind: 'text' },
|
|
33
39
|
{ kind: 'choice', count: MODELS.length },
|
|
34
40
|
{ kind: 'text' },
|
|
41
|
+
{ kind: 'choice', count: PRIORITY_CHOICES.length },
|
|
35
42
|
];
|
|
36
43
|
export function useSheetOverlay(deps) {
|
|
37
44
|
const { rows, columns, setMode, setNote, onAction } = deps;
|
|
@@ -51,6 +58,11 @@ export function useSheetOverlay(deps) {
|
|
|
51
58
|
// T117 — il prompt iniziale, EDITABILE. Quello che si legge nel campo è quello
|
|
52
59
|
// che parte: non un'anteprima di qualcos'altro.
|
|
53
60
|
const [prompt, setPrompt] = useState('');
|
|
61
|
+
// T158 — la conversazione nasce già marcata prioritaria? Si azzera a ogni
|
|
62
|
+
// apertura come gli altri campi: una marca armata che sopravvive alla chiusura
|
|
63
|
+
// farebbe partire prioritaria la conversazione successiva, senza che nulla a
|
|
64
|
+
// schermo lo dica.
|
|
65
|
+
const [priority, setPriority] = useState(false);
|
|
54
66
|
/** La task aperta è un cappello (`Size: Epic`)? Deciso UNA VOLTA all'apertura,
|
|
55
67
|
* dal testo che il detail ha già in mano: rifarlo a ogni cambio di azione
|
|
56
68
|
* riparserebbe l'intero task file per un campo dell'header. */
|
|
@@ -125,6 +137,7 @@ export function useSheetOverlay(deps) {
|
|
|
125
137
|
setModel(modelFor(catalog, kind) ?? MODEL_DEFAULT);
|
|
126
138
|
setSpawnNote('');
|
|
127
139
|
setPrompt(promptFor(catalog, kind, next.id));
|
|
140
|
+
setPriority(false);
|
|
128
141
|
setCursor({ row: DROW.action, caret: 0 });
|
|
129
142
|
setFind(null);
|
|
130
143
|
setOccIdx(0);
|
|
@@ -153,8 +166,21 @@ export function useSheetOverlay(deps) {
|
|
|
153
166
|
const fieldsIO = {
|
|
154
167
|
text: (row) => (row === DROW.prompt ? prompt : spawnNote),
|
|
155
168
|
setText: (row, next) => (row === DROW.prompt ? setPrompt(next) : setSpawnNote(next)),
|
|
156
|
-
choice: (row) =>
|
|
157
|
-
|
|
169
|
+
choice: (row) => row === DROW.action
|
|
170
|
+
? action
|
|
171
|
+
: row === DROW.priority
|
|
172
|
+
? priority
|
|
173
|
+
? 1
|
|
174
|
+
: 0
|
|
175
|
+
: Math.max(0, MODELS.indexOf(model)),
|
|
176
|
+
setChoice: (row, index) => {
|
|
177
|
+
if (row === DROW.action)
|
|
178
|
+
selectAction(index);
|
|
179
|
+
else if (row === DROW.priority)
|
|
180
|
+
setPriority(index === 1);
|
|
181
|
+
else
|
|
182
|
+
setModel(MODELS[index]);
|
|
183
|
+
},
|
|
158
184
|
};
|
|
159
185
|
function close() {
|
|
160
186
|
setMode('normal');
|
|
@@ -259,7 +285,7 @@ export function useSheetOverlay(deps) {
|
|
|
259
285
|
const text = prompt.trim();
|
|
260
286
|
close();
|
|
261
287
|
if (id)
|
|
262
|
-
onAction(id, specializeRecap(act.kind, epic), model, note, text);
|
|
288
|
+
onAction(id, specializeRecap(act.kind, epic), model, note, text, priority);
|
|
263
289
|
return;
|
|
264
290
|
}
|
|
265
291
|
if (key.pageUp) {
|
|
@@ -284,6 +310,7 @@ export function useSheetOverlay(deps) {
|
|
|
284
310
|
model,
|
|
285
311
|
spawnNote,
|
|
286
312
|
prompt,
|
|
313
|
+
priority,
|
|
287
314
|
cursor,
|
|
288
315
|
find,
|
|
289
316
|
lines,
|
package/dist/task-index.js
CHANGED
|
@@ -25,6 +25,18 @@ export function appendPin(projectRoot, sessionId, pinned) {
|
|
|
25
25
|
export function appendNote(projectRoot, sessionId, note) {
|
|
26
26
|
appendSessionRecord(projectRoot, { sessionId, note });
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* T158 — marca/smarca la conversazione come prioritaria: stessa forma di
|
|
30
|
+
* `appendPin`, quarto campo mutabile del record.
|
|
31
|
+
*
|
|
32
|
+
* Il campo lo leggono TRE attori a rilascio indipendente — il deck, compass e
|
|
33
|
+
* l'hook `announce-state.sh` del plugin. Il nome, una volta scritto nei file
|
|
34
|
+
* degli utenti, non si rinomina senza migrazione: è un contratto fra tre repo,
|
|
35
|
+
* non un dettaglio interno di questo modulo.
|
|
36
|
+
*/
|
|
37
|
+
export function appendPriority(projectRoot, sessionId, priority) {
|
|
38
|
+
appendSessionRecord(projectRoot, { sessionId, priority });
|
|
39
|
+
}
|
|
28
40
|
// Una sola lettura del JSONL per entrambe le mappe: il deck poll-a l'indice a
|
|
29
41
|
// ogni tick, leggere il file due volte raddoppierebbe l'I/O per nulla.
|
|
30
42
|
// Last-wins per campo (un re-pin dello stesso sessionId sovrascrive), e i due
|
|
@@ -35,12 +47,13 @@ export function loadSessionIndex(projectRoot) {
|
|
|
35
47
|
const forkOf = new Map();
|
|
36
48
|
const pinned = new Map();
|
|
37
49
|
const notes = new Map();
|
|
50
|
+
const priority = new Set();
|
|
38
51
|
let content;
|
|
39
52
|
try {
|
|
40
53
|
content = readFileSync(taskIndexPath(projectRoot), 'utf8');
|
|
41
54
|
}
|
|
42
55
|
catch {
|
|
43
|
-
return { bindings, forkOf, pinned, notes };
|
|
56
|
+
return { bindings, forkOf, pinned, notes, priority };
|
|
44
57
|
}
|
|
45
58
|
let order = 0; // posizione crescente dei record pinned → rango di pin (D2)
|
|
46
59
|
for (const line of content.split('\n')) {
|
|
@@ -85,10 +98,20 @@ export function loadSessionIndex(projectRoot) {
|
|
|
85
98
|
else
|
|
86
99
|
notes.delete(d.sessionId);
|
|
87
100
|
}
|
|
101
|
+
// T158 — stesso last-wins per campo: `false` è una smarcatura esplicita e
|
|
102
|
+
// toglie la chiave, il record che non nomina il campo non la tocca. Il
|
|
103
|
+
// `typeof` è quindi obbligatorio anche qui: un test di verità confonderebbe
|
|
104
|
+
// «smarcata» con «mai marcata», e la prima deve poter cancellare la seconda.
|
|
105
|
+
if (typeof d.priority === 'boolean') {
|
|
106
|
+
if (d.priority)
|
|
107
|
+
priority.add(d.sessionId);
|
|
108
|
+
else
|
|
109
|
+
priority.delete(d.sessionId);
|
|
110
|
+
}
|
|
88
111
|
}
|
|
89
112
|
catch {
|
|
90
113
|
// riga corrotta → skip
|
|
91
114
|
}
|
|
92
115
|
}
|
|
93
|
-
return { bindings, forkOf, pinned, notes };
|
|
116
|
+
return { bindings, forkOf, pinned, notes, priority };
|
|
94
117
|
}
|
package/dist/ui/detail-screen.js
CHANGED
|
@@ -7,7 +7,7 @@ import { sliceLine } from '../text-search.js';
|
|
|
7
7
|
import { sliceSpans } from '../markdown.js';
|
|
8
8
|
import { ChoiceRow, FieldText, LABEL_W } from './fields.js';
|
|
9
9
|
import { DETAIL_ACTIONS, MODELS } from '../spawn.js';
|
|
10
|
-
import { DROW } from '../overlays/sheet.js';
|
|
10
|
+
import { DROW, PRIORITY_CHOICES } from '../overlays/sheet.js';
|
|
11
11
|
import { CARET, CARET_OFF, WARN } from '../glyphs.js';
|
|
12
12
|
/** Resa di ogni costrutto markdown (T75 · D4): un solo livello di enfasi per
|
|
13
13
|
* costrutto, senza un secondo alfabeto da imparare. Heading uguali a ogni
|
|
@@ -70,7 +70,7 @@ const ACTION_KEYS = DETAIL_ACTIONS.map((a) => a.label[0]).join(' ');
|
|
|
70
70
|
* decidere QUALE azione lanciare, e con due overlay separati quella decisione
|
|
71
71
|
* costava uscire dal viewer e ricordarsi la combo.
|
|
72
72
|
*
|
|
73
|
-
* T117 — i
|
|
73
|
+
* T117 — i parametri dello spawn sono RIGHE con un fuoco solo,
|
|
74
74
|
* la stessa forma del modale edit: prima erano tre alfabeti diversi sulla stessa
|
|
75
75
|
* schermata (`←→` per l'azione, `tab` per il modello, i tasti nudi per la nota),
|
|
76
76
|
* e ogni parametro aggiunto ne chiedeva un quarto.
|
|
@@ -79,7 +79,7 @@ const ACTION_KEYS = DETAIL_ACTIONS.map((a) => a.label[0]).join(' ');
|
|
|
79
79
|
* rettangolo ha già coordinate e area cliccabile, quindi il layout sopravvive
|
|
80
80
|
* all'arrivo del mouse (T21 · SGR enable + hit-test) senza migrazione.
|
|
81
81
|
*/
|
|
82
|
-
export function DetailScreen({ id, title, missing, lines, spans, top, total, capacity, action, model, spawnNote, prompt, cursor, columns, find, occ, occCur, }) {
|
|
82
|
+
export function DetailScreen({ id, title, missing, lines, spans, top, total, capacity, action, model, spawnNote, prompt, priority, cursor, columns, find, occ, occCur, }) {
|
|
83
83
|
const last = Math.min(total, top + capacity);
|
|
84
84
|
// Il taglio lo fa il chiamante (invariante ③ di width.ts): le righe bottoni
|
|
85
85
|
// sono ASCII, quindi `truncate-end` oggi darebbe il risultato giusto per caso
|
|
@@ -91,5 +91,5 @@ export function DetailScreen({ id, title, missing, lines, spans, top, total, cap
|
|
|
91
91
|
// campo vuoto): riservarle sempre toglierebbe testo visibile a un titolo lungo
|
|
92
92
|
// per un avviso assente.
|
|
93
93
|
const textW = (extra) => Math.max(10, width - LABEL_W - CARET_OFF.length - extra);
|
|
94
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "loom-deck" }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: id }), " \u00B7 ", cut(title, Math.max(10, width - 34)), missing ? '' : ` · righe ${total === 0 ? 0 : top + 1}-${last} di ${total}`] }), find?.open ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193" }), " occorrenza \u00B7 ", _jsx(Text, { color: "yellow", children: "\u2190\u2192" }), " caret \u00B7", ' ', _jsx(Text, { color: "yellow", children: "^U" }), " svuota \u00B7 ", _jsx(Text, { color: "yellow", children: "\u23CE" }), " tieni \u00B7", ' ', _jsx(Text, { color: "yellow", children: "esc" }), " annulla"] })) : (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "PgUp/PgDn" }), " testo \u00B7", ' ', _jsx(Text, { color: "yellow", children: ACTION_KEYS }), " azione \u00B7", ' ', _jsx(Text, { color: "yellow", children: "^U" }), " svuota campo \u00B7 ", _jsx(Text, { color: "yellow", children: "^F" }), " cerca"] })), _jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: missing ? (_jsxs(Text, { color: "yellow", wrap: "truncate-end", children: [WARN, " task file non trovato \u00B7 le azioni restano attive (deck-run risolve la task per id)"] })) : (lines.map((l, i) => (_jsx(DetailLine, { line: l, spans: spans, occ: occ, current: occCur }, top + i)))) }), find?.open ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "cerca " }), _jsx(FieldText, { value: find.q, caret: find.caret, focused: true, cols: Math.max(10, width - 28) }), find.q.length === 0 ? (_jsx(Text, { dimColor: true, children: " \u00B7 digita per cercare" })) : occ.length === 0 ? (_jsx(Text, { color: "yellow", children: " \u00B7 nessuna occorrenza" })) : (_jsxs(Text, { color: "cyan", children: [' ', "\u00B7 ", occCur + 1, "/", occ.length] }))] }) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(ChoiceRow, { label: "azione", values: DETAIL_ACTIONS.map((a) => a.label), index: action, focused: cursor.row === DROW.action, width: width }), _jsxs(Text, { wrap: "truncate-end", children: [mark(DROW.prompt), _jsx(Text, { dimColor: true, children: 'prompt'.padEnd(LABEL_W) }), _jsx(FieldText, { value: prompt, caret: cursor.caret, focused: cursor.row === DROW.prompt, cols: textW(prompt ? 0 : PROMPT_HINT.length) }), prompt ? null : _jsx(Text, { dimColor: true, children: PROMPT_HINT })] }), _jsx(ChoiceRow, { label: "modello", values: MODELS, index: Math.max(0, MODELS.indexOf(model)), focused: cursor.row === DROW.model, width: width }), _jsxs(Text, { wrap: "truncate-end", children: [mark(DROW.title), _jsx(Text, { dimColor: true, children: 'titolo'.padEnd(LABEL_W) }), _jsx(FieldText, { value: spawnNote, caret: cursor.caret, focused: cursor.row === DROW.title, cols: textW(spawnNote ? 0 : NOTE_HINT.length) }), spawnNote ? null : _jsx(Text, { dimColor: true, children: NOTE_HINT })] })] })] }));
|
|
94
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "loom-deck" }), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", children: id }), " \u00B7 ", cut(title, Math.max(10, width - 34)), missing ? '' : ` · righe ${total === 0 ? 0 : top + 1}-${last} di ${total}`] }), find?.open ? (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "\u2191\u2193" }), " occorrenza \u00B7 ", _jsx(Text, { color: "yellow", children: "\u2190\u2192" }), " caret \u00B7", ' ', _jsx(Text, { color: "yellow", children: "^U" }), " svuota \u00B7 ", _jsx(Text, { color: "yellow", children: "\u23CE" }), " tieni \u00B7", ' ', _jsx(Text, { color: "yellow", children: "esc" }), " annulla"] })) : (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: "yellow", children: "PgUp/PgDn" }), " testo \u00B7", ' ', _jsx(Text, { color: "yellow", children: ACTION_KEYS }), " azione \u00B7", ' ', _jsx(Text, { color: "yellow", children: "^U" }), " svuota campo \u00B7 ", _jsx(Text, { color: "yellow", children: "^F" }), " cerca"] })), _jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginTop: 1, children: missing ? (_jsxs(Text, { color: "yellow", wrap: "truncate-end", children: [WARN, " task file non trovato \u00B7 le azioni restano attive (deck-run risolve la task per id)"] })) : (lines.map((l, i) => (_jsx(DetailLine, { line: l, spans: spans, occ: occ, current: occCur }, top + i)))) }), find?.open ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { dimColor: true, children: "cerca " }), _jsx(FieldText, { value: find.q, caret: find.caret, focused: true, cols: Math.max(10, width - 28) }), find.q.length === 0 ? (_jsx(Text, { dimColor: true, children: " \u00B7 digita per cercare" })) : occ.length === 0 ? (_jsx(Text, { color: "yellow", children: " \u00B7 nessuna occorrenza" })) : (_jsxs(Text, { color: "cyan", children: [' ', "\u00B7 ", occCur + 1, "/", occ.length] }))] }) })) : null, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(ChoiceRow, { label: "azione", values: DETAIL_ACTIONS.map((a) => a.label), index: action, focused: cursor.row === DROW.action, width: width }), _jsxs(Text, { wrap: "truncate-end", children: [mark(DROW.prompt), _jsx(Text, { dimColor: true, children: 'prompt'.padEnd(LABEL_W) }), _jsx(FieldText, { value: prompt, caret: cursor.caret, focused: cursor.row === DROW.prompt, cols: textW(prompt ? 0 : PROMPT_HINT.length) }), prompt ? null : _jsx(Text, { dimColor: true, children: PROMPT_HINT })] }), _jsx(ChoiceRow, { label: "modello", values: MODELS, index: Math.max(0, MODELS.indexOf(model)), focused: cursor.row === DROW.model, width: width }), _jsxs(Text, { wrap: "truncate-end", children: [mark(DROW.title), _jsx(Text, { dimColor: true, children: 'titolo'.padEnd(LABEL_W) }), _jsx(FieldText, { value: spawnNote, caret: cursor.caret, focused: cursor.row === DROW.title, cols: textW(spawnNote ? 0 : NOTE_HINT.length) }), spawnNote ? null : _jsx(Text, { dimColor: true, children: NOTE_HINT })] }), _jsx(ChoiceRow, { label: "priorit\u00E0", values: PRIORITY_CHOICES, index: priority ? 1 : 0, focused: cursor.row === DROW.priority, width: width })] })] }));
|
|
95
95
|
}
|
package/dist/ui/panes.js
CHANGED
|
@@ -148,7 +148,7 @@ export function InboxPane({ files, counts, activeView, paneCount, selectedPath,
|
|
|
148
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
149
|
}))] }));
|
|
150
150
|
}
|
|
151
|
-
export function SessionsPane({ parentLabel, isSpot, isAll, bindings, taskW, ageW, rows, counts, activeView, paneCount, selectedId, focused, above, below, columns, forkOf, sessionNotes, projectCore, live, }) {
|
|
151
|
+
export function SessionsPane({ parentLabel, isSpot, isAll, bindings, taskW, ageW, rows, counts, activeView, paneCount, selectedId, focused, above, below, columns, forkOf, sessionNotes, sessionPriority, projectCore, live, }) {
|
|
152
152
|
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
153
|
// T100 — la nota della vista di default resta quella storica, che nomina
|
|
154
154
|
// il PARENT (task, spot o tutte); le altre tre viste portano la propria,
|
|
@@ -236,7 +236,18 @@ export function SessionsPane({ parentLabel, isSpot, isAll, bindings, taskW, ageW
|
|
|
236
236
|
// metterlo fuori cella sposterebbe il bordo del titolo solo sui rami —
|
|
237
237
|
// cioè rimetterebbe lo slittamento che le colonne tolgono.
|
|
238
238
|
const forkMark = forked ? '⑂ ' : '';
|
|
239
|
-
|
|
239
|
+
// T158 · P7 — 🚨 entra nella stessa cella e per la stessa ragione,
|
|
240
|
+
// davanti al segno di fork. NON nella cella di appartenenza (📌/🔗/○):
|
|
241
|
+
// lì un solo glifo ha diritto di stare, e la priorità coprirebbe il pin
|
|
242
|
+
// sulle righe che sono pinnate e prioritarie insieme. La cella titolo
|
|
243
|
+
// perde 3 colonne solo sulle righe marcate.
|
|
244
|
+
//
|
|
245
|
+
// Larghezza misurata concorde (2 colonne per `string-width` e 2 per il
|
|
246
|
+
// terminale) e carattere astrale: nessun selettore di variazione, quindi
|
|
247
|
+
// nessuna voce da aggiungere alla tabella dei glifi discordi.
|
|
248
|
+
const priorityMark = sessionPriority.has(s.sessionId) ? '🚨 ' : '';
|
|
249
|
+
const marks = priorityMark + forkMark;
|
|
250
|
+
const inner = Math.max(0, titleW - termWidth(marks));
|
|
240
251
|
// T60 — il testo arriva già ripulito di ciò che le colonne accanto
|
|
241
252
|
// dicono già (progetto e task id): senza, la cella conterrebbe
|
|
242
253
|
// `🧵 loom-works · T59` accanto a una colonna che dice `T59`.
|
|
@@ -244,6 +255,6 @@ export function SessionsPane({ parentLabel, isSpot, isAll, bindings, taskW, ageW
|
|
|
244
255
|
const used = (label.note ? termWidth(label.note) : 0) +
|
|
245
256
|
(label.note && label.rest ? 1 : 0) +
|
|
246
257
|
termWidth(label.rest);
|
|
247
|
-
return (_jsxs(Text, { inverse: sel && focused, bold: sel && !focused, wrap: "truncate-end", children: [sel ? CARET : CARET_OFF, isPinnedRow ? (_jsx(Text, { color: "yellow", children: pad('📌', 2) })) : linked ? (_jsx(Text, { color: "green", children: pad('🔗', 2) })) : (_jsx(Text, { dimColor: true, children: pad('○', 2) })), ' ', _jsx(Text, { color: liveEntry ? (liveEntry.status === 'busy' ? 'yellow' : 'green') : undefined, children: liveEntry ? (liveEntry.status === 'busy' ? LIVE_BUSY : LIVE_IDLE) : LIVE_NONE }), _jsx(Text, { color: liveEntry ? (liveEntry.status === 'busy' ? 'yellow' : 'green') : 'cyan', bold: Boolean(liveEntry), children: s.sessionId.slice(0, SID_CHARS) }), ' ', _jsx(Text, { dimColor: !s.model, children: pad(modelShort(s.model), MODEL_W) }), ' ', taskW > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: bound && taskCell ? 'green' : undefined, dimColor: !bound, children: pad(taskCell, taskW) }), ' '] })) : null, forkMark ? _jsx(Text, { color: "magenta", children: forkMark }) : null, label.note ? (_jsx(Text, { color: "yellow", bold: true, children: label.note })) : null, label.note && label.rest ? ' ' : null, label.rest ? _jsx(Text, { dimColor: Boolean(label.note), children: label.rest }) : null, ' '.repeat(Math.max(0, inner - used)), ' ', _jsx(Text, { dimColor: true, children: pad(age, ageW, 'right') })] }, s.sessionId));
|
|
258
|
+
return (_jsxs(Text, { inverse: sel && focused, bold: sel && !focused, wrap: "truncate-end", children: [sel ? CARET : CARET_OFF, isPinnedRow ? (_jsx(Text, { color: "yellow", children: pad('📌', 2) })) : linked ? (_jsx(Text, { color: "green", children: pad('🔗', 2) })) : (_jsx(Text, { dimColor: true, children: pad('○', 2) })), ' ', _jsx(Text, { color: liveEntry ? (liveEntry.status === 'busy' ? 'yellow' : 'green') : undefined, children: liveEntry ? (liveEntry.status === 'busy' ? LIVE_BUSY : LIVE_IDLE) : LIVE_NONE }), _jsx(Text, { color: liveEntry ? (liveEntry.status === 'busy' ? 'yellow' : 'green') : 'cyan', bold: Boolean(liveEntry), children: s.sessionId.slice(0, SID_CHARS) }), ' ', _jsx(Text, { dimColor: !s.model, children: pad(modelShort(s.model), MODEL_W) }), ' ', taskW > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: bound && taskCell ? 'green' : undefined, dimColor: !bound, children: pad(taskCell, taskW) }), ' '] })) : null, priorityMark ? _jsx(Text, { color: "red", bold: true, children: priorityMark }) : null, forkMark ? _jsx(Text, { color: "magenta", children: forkMark }) : null, label.note ? (_jsx(Text, { color: "yellow", bold: true, children: label.note })) : null, label.note && label.rest ? ' ' : null, label.rest ? _jsx(Text, { dimColor: Boolean(label.note), children: label.rest }) : null, ' '.repeat(Math.max(0, inner - used)), ' ', _jsx(Text, { dimColor: true, children: pad(age, ageW, 'right') })] }, s.sessionId));
|
|
248
259
|
}))] }));
|
|
249
260
|
}
|
package/dist/ui/screens.js
CHANGED
|
@@ -149,7 +149,7 @@ export function screenFor(input) {
|
|
|
149
149
|
// posizione è lo scroll mosso a mano. Il clamp serve comunque — un resize
|
|
150
150
|
// può accorciare il testo sotto uno scroll già dato.
|
|
151
151
|
const start = Math.min(sheet.top, sheet.maxTop);
|
|
152
|
-
return (_jsx(DetailScreen, { id: sheet.sheet.id, title: sheet.sheet.title, missing: sheet.sheet.text === null, lines: sheet.lines.slice(start, start + sheet.capacity), spans: sheet.doc?.spans ?? [], top: start, total: sheet.lines.length, capacity: sheet.capacity, action: sheet.action, model: sheet.model, spawnNote: sheet.spawnNote, prompt: sheet.prompt, cursor: sheet.cursor, columns: columns, find: sheet.find, occ: sheet.findRes.occ, occCur: sheet.occCur }));
|
|
152
|
+
return (_jsx(DetailScreen, { id: sheet.sheet.id, title: sheet.sheet.title, missing: sheet.sheet.text === null, lines: sheet.lines.slice(start, start + sheet.capacity), spans: sheet.doc?.spans ?? [], top: start, total: sheet.lines.length, capacity: sheet.capacity, action: sheet.action, model: sheet.model, spawnNote: sheet.spawnNote, prompt: sheet.prompt, priority: sheet.priority, cursor: sheet.cursor, columns: columns, find: sheet.find, occ: sheet.findRes.occ, occCur: sheet.occCur }));
|
|
153
153
|
}
|
|
154
154
|
// ── T121 · viewer del project status ────────────────────────────────────
|
|
155
155
|
// Quinta schermata sostitutiva, stessa ragione delle altre quattro: un recap
|
package/dist/viewport.js
CHANGED
|
@@ -264,17 +264,18 @@ export function readerCapacity(rows) {
|
|
|
264
264
|
// 1 riga prompt (T117)
|
|
265
265
|
// 1 riga modello (T108)
|
|
266
266
|
// 1 riga titolo (T111)
|
|
267
|
+
// 1 riga priorità (T158)
|
|
267
268
|
//
|
|
268
|
-
// Le ultime
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
269
|
+
// Le ultime sei sono il motivo per cui il detail non può riusare READER_CHROME:
|
|
270
|
+
// le cinque righe dell'area di compilazione sono righe FISSE in più dentro
|
|
271
|
+
// l'overlay, e ogni riga fissa aggiunta va scalata dalla capienza del contenuto o
|
|
272
|
+
// il frame sfonda `rows` (stessa invariante di TASKS_PANE_CHROME).
|
|
272
273
|
//
|
|
273
274
|
// Sono fisse e non `extra` condizionali di `detailCapacity` (T111 · D1): due
|
|
274
275
|
// condizionali mutuamente esclusivi obbligherebbero il parametro a dire QUALE è
|
|
275
276
|
// a schermo — un solo booleano per entrambi sottostima quando quello aperto è il
|
|
276
277
|
// più alto, e sommarli toglie righe al testo per un campo che non c'è.
|
|
277
|
-
const DETAIL_CHROME =
|
|
278
|
+
const DETAIL_CHROME = 14;
|
|
278
279
|
// T91 — la ricerca dentro il detail: marginTop + riga del campo.
|
|
279
280
|
//
|
|
280
281
|
// `MODAL_HEIGHT` dice che il detail costa 0 ai due pane (li sostituisce), e da
|
package/package.json
CHANGED