@cccarv82/freya 2.13.1 → 2.13.3

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/cli/web-ui.css CHANGED
@@ -134,6 +134,7 @@ body {
134
134
  .shell {
135
135
  display: grid;
136
136
  grid-template-columns: 72px minmax(520px, 1fr);
137
+ grid-template-rows: 1fr;
137
138
  height: 100vh;
138
139
  min-height: 0;
139
140
  }
@@ -360,6 +361,7 @@ body {
360
361
  display: flex;
361
362
  flex-direction: column;
362
363
  min-height: 0;
364
+ overflow: hidden;
363
365
  padding: 8px 0 18px;
364
366
  }
365
367
 
@@ -588,6 +590,7 @@ body {
588
590
  display: flex;
589
591
  flex-direction: column;
590
592
  gap: 18px;
593
+ flex: 1;
591
594
  }
592
595
 
593
596
  .promptShell {
package/cli/web-ui.js CHANGED
@@ -162,6 +162,116 @@
162
162
  return html;
163
163
  }
164
164
 
165
+ function formatPlanForDisplay(rawPlan) {
166
+ var text = String(rawPlan || '');
167
+ if (!text) return null;
168
+
169
+ // --- Strategy 1: try full JSON parse (works when copilot output is complete) ---
170
+ try {
171
+ var start = text.indexOf('{');
172
+ if (start !== -1) {
173
+ var depth = 0, inStr = false, esc = false, jsonStr = null;
174
+ for (var i = start; i < text.length; i++) {
175
+ var ch = text[i];
176
+ if (esc) { esc = false; continue; }
177
+ if (ch === '\\') { esc = true; continue; }
178
+ if (ch === '"') { inStr = !inStr; continue; }
179
+ if (inStr) continue;
180
+ if (ch === '{') depth++;
181
+ if (ch === '}') { depth--; if (depth === 0) { jsonStr = text.slice(start, i + 1); break; } }
182
+ }
183
+ if (jsonStr) {
184
+ var plan = JSON.parse(jsonStr);
185
+ var actions = Array.isArray(plan.actions) ? plan.actions : [];
186
+ if (actions.length > 0) {
187
+ return formatActions(actions);
188
+ }
189
+ }
190
+ }
191
+ } catch (e) { /* fall through to regex strategy */ }
192
+
193
+ // --- Strategy 2: regex fallback for truncated/malformed JSON ---
194
+ var lines = [];
195
+ var num = 0;
196
+
197
+ // Match append_daily_log / appenddailylog actions
198
+ var logRe = /"type"\s*:\s*"append_?daily_?log"\s*,\s*"text"\s*:\s*"([^"]{1,300})/gi;
199
+ var m;
200
+ while ((m = logRe.exec(text)) !== null) {
201
+ num++;
202
+ var t = m[1].slice(0, 140);
203
+ lines.push(num + '. \u{1F4DD} **Registrar no log:** ' + t + (m[1].length > 140 ? '...' : ''));
204
+ }
205
+
206
+ // Match create_task actions
207
+ var taskRe = /"type"\s*:\s*"create_?task"\s*,\s*"description"\s*:\s*"([^"]{1,200})/gi;
208
+ while ((m = taskRe.exec(text)) !== null) {
209
+ num++;
210
+ var desc = m[1].slice(0, 120);
211
+ var priMatch = text.slice(m.index, m.index + 400).match(/"priority"\s*:\s*"(\w+)"/i);
212
+ var pri = priMatch ? ' (prioridade: **' + priMatch[1].toUpperCase() + '**)' : '';
213
+ lines.push(num + '. \u2705 **Criar tarefa:** ' + desc + pri);
214
+ }
215
+
216
+ // Match create_blocker actions
217
+ var blockerRe = /"type"\s*:\s*"create_?blocker"\s*,\s*"title"\s*:\s*"([^"]{1,200})/gi;
218
+ while ((m = blockerRe.exec(text)) !== null) {
219
+ num++;
220
+ var title = m[1].slice(0, 120);
221
+ var sevMatch = text.slice(m.index, m.index + 400).match(/"severity"\s*:\s*"(\w+)"/i);
222
+ var sev = sevMatch ? ' (severidade: **' + sevMatch[1].toUpperCase() + '**)' : '';
223
+ lines.push(num + '. \u{1F6A7} **Registrar blocker:** ' + title + sev);
224
+ }
225
+
226
+ // Match suggest_report actions
227
+ var repRe = /"type"\s*:\s*"suggest_?report"\s*,\s*"name"\s*:\s*"([^"]+)"/gi;
228
+ while ((m = repRe.exec(text)) !== null) {
229
+ num++;
230
+ lines.push(num + '. \u{1F4CA} **Sugerir relatorio:** ' + m[1]);
231
+ }
232
+
233
+ return lines.length > 0 ? lines.join('\n') : null;
234
+ }
235
+
236
+ function formatActions(actions) {
237
+ var icons = {
238
+ appenddailylog: '\u{1F4DD}', createtask: '\u2705',
239
+ createblocker: '\u{1F6A7}', suggestreport: '\u{1F4CA}',
240
+ oraclequery: '\u{1F50D}'
241
+ };
242
+
243
+ var lines = actions.map(function(a, i) {
244
+ var type = String(a.type || '').trim().toLowerCase().replace(/_/g, '');
245
+ var icon = icons[type] || '\u2022';
246
+ var num = i + 1;
247
+
248
+ if (type === 'appenddailylog') {
249
+ var t = String(a.text || '').slice(0, 140);
250
+ return num + '. ' + icon + ' **Registrar no log:** ' + t + (String(a.text || '').length > 140 ? '...' : '');
251
+ }
252
+ if (type === 'createtask') {
253
+ var desc = String(a.description || '').slice(0, 120);
254
+ var pri = a.priority ? ' (prioridade: **' + String(a.priority).toUpperCase() + '**)' : '';
255
+ var cat = a.category ? ' [' + a.category + ']' : '';
256
+ return num + '. ' + icon + ' **Criar tarefa:** ' + desc + pri + cat;
257
+ }
258
+ if (type === 'createblocker') {
259
+ var title = String(a.title || a.description || '').slice(0, 120);
260
+ var sev = a.severity ? ' (severidade: **' + String(a.severity).toUpperCase() + '**)' : '';
261
+ return num + '. ' + icon + ' **Registrar blocker:** ' + title + sev;
262
+ }
263
+ if (type === 'suggestreport') {
264
+ return num + '. ' + icon + ' **Sugerir relatorio:** ' + String(a.name || a.reportType || '');
265
+ }
266
+ if (type === 'oraclequery') {
267
+ return num + '. ' + icon + ' **Consultar oracle:** ' + String(a.query || '').slice(0, 120);
268
+ }
269
+ return num + '. \u2022 **' + String(a.type || 'acao') + '**';
270
+ });
271
+
272
+ return lines.join('\n');
273
+ }
274
+
165
275
  function ensureChatSession() {
166
276
  if (state.chatSessionId) return state.chatSessionId;
167
277
  try {
@@ -1507,29 +1617,63 @@
1507
1617
  + (t.category ? ' · <span style="font-family:var(--mono);">' + escapeHtml(t.category) + '</span>' : '')
1508
1618
  + '</div>'
1509
1619
  + '</div></div>'
1510
- + '<div style="display:flex; gap:6px; flex-shrink:0;">'
1620
+ + '<div class="task-actions" style="display:flex; gap:6px; flex-shrink:0; align-items:center;">'
1511
1621
  + '<button class="btn small complete-btn" type="button" style="padding:3px 10px; font-size:11px;" title="Marcar como concluída">✓ Concluir</button>'
1512
1622
  + '<button class="btn small edit-btn" type="button" style="padding:3px 8px; font-size:11px;">✎</button>'
1513
1623
  + '</div>';
1514
1624
  row.querySelector('.edit-btn').onclick = () => editTask(t);
1515
- row.querySelector('.complete-btn').onclick = async () => {
1516
- const btn = row.querySelector('.complete-btn');
1517
- btn.disabled = true;
1518
- btn.textContent = '…';
1519
- try {
1520
- setPill('run', 'concluindo…');
1521
- await api('/api/tasks/complete', { dir: dirOrDefault(), id: t.id });
1522
- row.style.opacity = '0.4';
1523
- row.style.pointerEvents = 'none';
1524
- await refreshToday();
1525
- setPill('ok', 'concluída');
1526
- setTimeout(() => setPill('ok', 'pronto'), 800);
1527
- } catch (e) {
1528
- btn.disabled = false;
1529
- btn.textContent = '✓ Concluir';
1530
- setPill('err', 'falhou');
1531
- }
1625
+
1626
+ var attachCompleteHandler = function() {
1627
+ var cBtn = row.querySelector('.complete-btn');
1628
+ if (!cBtn) return;
1629
+ cBtn.onclick = function() {
1630
+ var actionsDiv = row.querySelector('.task-actions');
1631
+ actionsDiv.innerHTML =
1632
+ '<input type="text" class="comment-input" placeholder="Comentario (opcional)" '
1633
+ + 'style="font-size:11px; padding:3px 8px; background:var(--bg); border:1px solid var(--border); '
1634
+ + 'color:var(--text); width:180px; outline:none; font-family:var(--mono);" />'
1635
+ + '<button class="btn small confirm-btn" type="button" style="padding:3px 10px; font-size:11px; '
1636
+ + 'background:var(--accent); color:#000; font-weight:700;">Confirmar</button>'
1637
+ + '<button class="btn small cancel-btn" type="button" style="padding:3px 8px; font-size:11px; '
1638
+ + 'color:var(--muted);">\u2715</button>';
1639
+
1640
+ var input = actionsDiv.querySelector('.comment-input');
1641
+ input.focus();
1642
+
1643
+ var doComplete = async function() {
1644
+ var comment = input.value.trim();
1645
+ actionsDiv.querySelector('.confirm-btn').disabled = true;
1646
+ actionsDiv.querySelector('.confirm-btn').textContent = '\u2026';
1647
+ try {
1648
+ setPill('run', 'concluindo\u2026');
1649
+ var body = { dir: dirOrDefault(), id: t.id };
1650
+ if (comment) body.comment = comment;
1651
+ await api('/api/tasks/complete', body);
1652
+ row.style.opacity = '0.4';
1653
+ row.style.pointerEvents = 'none';
1654
+ await refreshToday();
1655
+ setPill('ok', 'conclu\u00edda');
1656
+ setTimeout(function() { setPill('ok', 'pronto'); }, 800);
1657
+ } catch (e) {
1658
+ actionsDiv.querySelector('.confirm-btn').disabled = false;
1659
+ actionsDiv.querySelector('.confirm-btn').textContent = 'Confirmar';
1660
+ setPill('err', 'falhou');
1661
+ }
1662
+ };
1663
+
1664
+ actionsDiv.querySelector('.confirm-btn').onclick = doComplete;
1665
+ input.onkeydown = function(e) { if (e.key === 'Enter') doComplete(); };
1666
+ actionsDiv.querySelector('.cancel-btn').onclick = function() {
1667
+ actionsDiv.innerHTML =
1668
+ '<button class="btn small complete-btn" type="button" style="padding:3px 10px; font-size:11px;" '
1669
+ + 'title="Marcar como conclu\u00edda">\u2713 Concluir</button>'
1670
+ + '<button class="btn small edit-btn" type="button" style="padding:3px 8px; font-size:11px;">\u270E</button>';
1671
+ actionsDiv.querySelector('.edit-btn').onclick = function() { editTask(t); };
1672
+ attachCompleteHandler();
1673
+ };
1674
+ };
1532
1675
  };
1676
+ attachCompleteHandler();
1533
1677
  return row;
1534
1678
  };
1535
1679
 
@@ -2328,7 +2472,8 @@
2328
2472
 
2329
2473
  // Show plan output in Preview panel
2330
2474
  const header = r.ok === false ? '## Agent Plan (planner unavailable)\n\n' : '## Agent Plan (draft)\n\n';
2331
- const planOut = header + (r.plan || '');
2475
+ const formatted = r.ok !== false ? formatPlanForDisplay(r.plan) : null;
2476
+ const planOut = header + (formatted || r.plan || '');
2332
2477
  setOut(planOut);
2333
2478
  chatAppend('assistant', planOut, { markdown: true });
2334
2479
  ta.value = '';
package/cli/web.js CHANGED
@@ -3620,7 +3620,8 @@ async function cmdWeb({ port, dir, open, dev }) {
3620
3620
  completedAt: t.completed_at,
3621
3621
  projectSlug: t.project_slug,
3622
3622
  priority: meta.priority,
3623
- streamSlug: meta.streamSlug
3623
+ streamSlug: meta.streamSlug,
3624
+ comments: Array.isArray(meta.comments) ? meta.comments : []
3624
3625
  };
3625
3626
  });
3626
3627
 
@@ -3631,10 +3632,24 @@ async function cmdWeb({ port, dir, open, dev }) {
3631
3632
  const id = String(payload.id || '').trim();
3632
3633
  if (!id) return safeJson(res, 400, { error: 'Missing id' });
3633
3634
 
3635
+ const comment = typeof payload.comment === 'string' ? payload.comment.trim() : '';
3634
3636
  const now = new Date().toISOString();
3635
- const info = dl.db.prepare(`UPDATE tasks SET status = 'COMPLETED', completed_at = ? WHERE id = ?`).run(now, id);
3636
3637
 
3637
- if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
3638
+ if (comment) {
3639
+ const row = dl.db.prepare('SELECT metadata FROM tasks WHERE id = ?').get(id);
3640
+ if (!row) return safeJson(res, 404, { error: 'Task not found' });
3641
+ let meta = {};
3642
+ try { meta = row.metadata ? JSON.parse(row.metadata) : {}; } catch (_) { meta = {}; }
3643
+ if (!Array.isArray(meta.comments)) meta.comments = [];
3644
+ meta.comments.push({ text: comment, createdAt: now });
3645
+ const info = dl.db.prepare(`UPDATE tasks SET status = 'COMPLETED', completed_at = ?, metadata = ? WHERE id = ?`)
3646
+ .run(now, JSON.stringify(meta), id);
3647
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
3648
+ } else {
3649
+ const info = dl.db.prepare(`UPDATE tasks SET status = 'COMPLETED', completed_at = ? WHERE id = ?`).run(now, id);
3650
+ if (info.changes === 0) return safeJson(res, 404, { error: 'Task not found' });
3651
+ }
3652
+
3638
3653
  return safeJson(res, 200, { ok: true, task: { id, status: 'COMPLETED', completedAt: now } });
3639
3654
  }
3640
3655
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cccarv82/freya",
3
- "version": "2.13.1",
3
+ "version": "2.13.3",
4
4
  "description": "Personal AI Assistant with local-first persistence",
5
5
  "scripts": {
6
6
  "health": "node scripts/validate-data.js && node scripts/validate-structure.js",