@lamemind/loom-deck 0.73.0 → 0.73.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/task-edit.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // isolata in `writeTaskEdit`, così la grammatica delle sostituzioni è testabile
4
4
  // senza toccare il filesystem.
5
5
  import { readFileSync, writeFileSync } from 'node:fs';
6
- import { TASK_ID_RE, findTaskFile, headerColumns } from './tasks.js';
6
+ import { TASK_ID_RE, findTaskFile, overviewRows } from './tasks.js';
7
7
  // Il task file scrive la priorità per NOME (`- **Priority**: Med`), tasks.md la
8
8
  // scrive come GLIFO (colonna Pri). Due rappresentazioni dello stesso valore →
9
9
  // due mappe, entrambe keyed sul nome canonico di view.ts (unica fonte del rango).
@@ -83,10 +83,13 @@ export function initialDetail(current, prog, date = today()) {
83
83
  * toccate restano i token grezzi originali: nessun re-flow della tabella, il
84
84
  * diff resta di una riga. `ok:false` = id assente → il chiamante non scrive nulla.
85
85
  *
86
- * Le posizioni vengono dall'HEADER (`headerColumns`, stessa fonte di
87
- * `parseTasks`), mai da un indice cablato: qui si SCRIVE su disco, quindi una
88
- * colonna in più o in meno non produrrebbe una lettura sbagliata ma un titolo
89
- * sovrascritto col glifo. Header assente → `ok:false`, non si tira a indovinare.
86
+ * Le righe candidate vengono da `overviewRows`, lo stesso confine di
87
+ * `parseTasks`: solo dentro la Tasks Overview, con le colonne del suo HEADER e
88
+ * mai un indice cablato. Qui si SCRIVE su disco, quindi senza confine una riga
89
+ * cross-deps con lo stesso id (`| T07 | — | ✔️T08 |`) sopra la tabella verrebbe
90
+ * riscritta al posto della riga task, con Pri/Prog/titolo sopra le celle Parent e
91
+ * Cross Deps; e una colonna in più o in meno darebbe un titolo sovrascritto col
92
+ * glifo. Header assente → nessuna riga → `ok:false`, non si tira a indovinare.
90
93
  *
91
94
  * `desc` undefined ≠ `desc` vuota: la prima non tocca la cella (chiamata
92
95
  * legacy, solo pri/prog), la seconda la svuota davvero.
@@ -104,23 +107,14 @@ export function updateTasksMdRow(content, id, priGlyph, progGlyph, desc) {
104
107
  if (!TASK_ID_RE.test(id))
105
108
  return { content, ok: false };
106
109
  const lines = content.split('\n');
107
- const cols = lines.reduce((found, l) => found ?? headerColumns(l), null);
108
- if (!cols)
109
- return { content, ok: false };
110
- let ok = false;
111
- const out = lines.map((line) => {
112
- if (ok)
113
- return line;
114
- if (!line.trim().startsWith('|'))
115
- return line;
110
+ for (const { index, line, id: rowId, cols } of overviewRows(lines)) {
111
+ if (rowId !== id)
112
+ continue;
116
113
  const cells = line.split('|');
117
- if ((cells[1] ?? '').trim() !== id)
118
- return line;
119
114
  // Serve la cella desc PIÙ la coda dopo il `|` finale: senza la coda lo
120
115
  // splice inserirebbe invece di sostituire.
121
116
  if (cells.length < cols.desc + 2)
122
- return line; // riga malformata → non toccarla
123
- ok = true;
117
+ continue; // riga malformata → non toccarla
124
118
  cells[cols.pri] = ` ${priGlyph} `;
125
119
  cells[cols.prog] = ` ${progGlyph} `;
126
120
  if (desc !== undefined) {
@@ -128,9 +122,10 @@ export function updateTasksMdRow(content, id, priGlyph, progGlyph, desc) {
128
122
  // ben formata) e va conservata, o la riga perderebbe il bordo destro.
129
123
  cells.splice(cols.desc, cells.length - cols.desc - 1, ` ${sanitizeCell(desc)} `);
130
124
  }
131
- return cells.join('|');
132
- });
133
- return { content: ok ? out.join('\n') : content, ok };
125
+ lines[index] = cells.join('|');
126
+ return { content: lines.join('\n'), ok: true };
127
+ }
128
+ return { content, ok: false };
134
129
  }
135
130
  /**
136
131
  * Rende un testo scrivibile dentro una CELLA di tabella markdown. Due caratteri
package/dist/tasks.js CHANGED
@@ -59,31 +59,60 @@ export function headerColumns(line) {
59
59
  return null;
60
60
  return { pri, prog, desc: prog + 1 };
61
61
  }
62
- // Estrae le righe `| Tnn | Pri | Prog | Task |` della Tasks Overview. Righe
63
- // header/separatore non matchano `^T\d+$` → scartate.
64
- //
65
- // Le righe si parsano solo DOPO aver incontrato l'header: senza indici derivati
66
- // non si tira a indovinare, la lista resta vuota. Un fallimento visibile invece
67
- // di una cella letta al posto sbagliato.
68
- export function parseTasks(content) {
69
- const tasks = [];
62
+ /**
63
+ * Le righe task della Tasks Overview: FONTE UNICA del confine della tabella,
64
+ * condivisa da `parseTasks` (lettura) e `updateTasksMdRow` (scrittura su disco),
65
+ * come `TASK_ID_RE` e `headerColumns` — due confini scritti in due posti
66
+ * divergerebbero, e quello dello scrittore è quello che fa danno.
67
+ *
68
+ * La tabella apre all'header (`headerColumns`) e chiude alla PRIMA riga che non
69
+ * inizia con `|`; un secondo header ne riapre una, con le proprie colonne. Il
70
+ * confine serve perché la prima cella `^T\d+$` non basta a riconoscere una riga
71
+ * task: le righe della tabella cross-deps dell'Execution Plan
72
+ * (`| T07 | — | ✔️T08 |`) hanno la stessa forma appena perdono il 🔒 davanti
73
+ * all'id, e senza confine diventavano task doppie con Pri = cella Parent e
74
+ * Prog = cella Cross Deps.
75
+ *
76
+ * Il titolo `## Tasks Overview` non entra nel criterio: il gate è l'header, così
77
+ * il deck non dipende dal nome della sezione. Il prezzo: una riga vuota o un
78
+ * commento DENTRO la tabella la chiude, e le righe sotto spariscono dal deck
79
+ * senza errore.
80
+ */
81
+ export function overviewRows(lines) {
82
+ const rows = [];
70
83
  let cols = null;
71
- for (const line of content.split('\n')) {
84
+ lines.forEach((line, index) => {
72
85
  const header = headerColumns(line);
73
86
  if (header) {
74
87
  cols = header;
75
- continue;
88
+ return;
76
89
  }
77
90
  if (!cols)
78
- continue;
91
+ return;
79
92
  const t = line.trim();
80
- if (!t.startsWith('|'))
81
- continue;
82
- const cells = t.split('|').map((c) => c.trim());
93
+ if (!t.startsWith('|')) {
94
+ cols = null;
95
+ return;
96
+ }
97
+ // cells[0] = '' (prima del primo |): l'id è la seconda cella. Header e
98
+ // separatore non matchano `^T\d+$` → scartati.
99
+ const id = (t.split('|')[1] ?? '').trim();
100
+ if (TASK_ID_RE.test(id))
101
+ rows.push({ index, line, id, cols });
102
+ });
103
+ return rows;
104
+ }
105
+ // Estrae le righe `| Tnn | Pri | Prog | Task |` della Tasks Overview — il
106
+ // confine della tabella sta in `overviewRows`.
107
+ //
108
+ // Le righe si parsano solo DOPO aver incontrato l'header: senza indici derivati
109
+ // non si tira a indovinare, la lista resta vuota. Un fallimento visibile invece
110
+ // di una cella letta al posto sbagliato.
111
+ export function parseTasks(content) {
112
+ const tasks = [];
113
+ for (const { line, id, cols } of overviewRows(content.split('\n'))) {
83
114
  // cells[0] = '' (prima del primo |), cells[last] = '' (dopo l'ultimo |)
84
- const id = cells[1];
85
- if (!TASK_ID_RE.test(id))
86
- continue;
115
+ const cells = line.trim().split('|').map((c) => c.trim());
87
116
  const pri = cells[cols.pri] ?? '';
88
117
  const prog = cells[cols.prog] ?? '';
89
118
  // desc = colonna finale; join per resistere a eventuali `|` nella descrizione.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamemind/loom-deck",
3
- "version": "0.73.0",
3
+ "version": "0.73.1",
4
4
  "description": "Deck TUI Ink per-progetto della famiglia loom: legge tasks.md e spawna sessioni Claude Code bound via LOOM_TASK",
5
5
  "type": "module",
6
6
  "bin": {