@danieltmn/tamnora 0.11.0 → 0.11.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ Todos los cambios relevantes de este proyecto se documentan en este archivo.
5
5
  El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/)
6
6
  y el proyecto usa [Versionado Semantico](https://semver.org/lang/es/).
7
7
 
8
+ ## [0.11.2] - 2026-10-09
9
+
10
+ ### Arreglado
11
+
12
+ - **CI en Node 22**: el backoff de `withRetry` usaba un timer `unref`, que dejaba
13
+ que el event loop se vaciara y `node:test` cancelara los tests de
14
+ `resilience.test.js` ("Promise resolution is still pending..."). Se quito el
15
+ `unref`: ahora el reintento mantiene el loop vivo y la suite pasa en Node 22.
16
+
17
+ ## [0.11.1] - 2026-10-09
18
+
19
+ ### Arreglado
20
+
21
+ - **Consola en movil**: el input del chat ahora es un campo visible en su propia
22
+ linea (antes flotaba sin fondo y casi no se notaba); las cards de MCP/Skills
23
+ (y tambien proveedores y procesos) llevan las acciones a su propia fila y la
24
+ descripcion se recorta a dos lineas en vez de apretarse contra los botones.
25
+
8
26
  ## [0.11.0] - 2026-10-09
9
27
 
10
28
  ### Quitado
@@ -26,8 +26,10 @@ export function isRetryableError(err) {
26
26
 
27
27
  function sleep(ms, signal) {
28
28
  return new Promise((resolve, reject) => {
29
+ // Sin `unref`: en Node 22 un timer sin referencias deja que el event loop se
30
+ // vacíe y `node:test` cancela los tests ("Promise resolution is still
31
+ // pending..."). Si abortan, el listener de abajo lo limpia.
29
32
  const timer = setTimeout(resolve, ms);
30
- timer.unref?.();
31
33
  if (!signal) return;
32
34
  if (signal.aborted) {
33
35
  clearTimeout(timer);
@@ -3577,9 +3577,11 @@ function renderProviderList() {
3577
3577
  )
3578
3578
  );
3579
3579
  li.appendChild(main);
3580
- li.appendChild(button('usar', (event) => { event.stopPropagation(); useProvider(p.id); }));
3581
- li.appendChild(button('editar', (event) => { event.stopPropagation(); openProviderForm(p); }));
3582
- if (p.stored) li.appendChild(button('borrar', (event) => { event.stopPropagation(); removeProvider(p.id); }, 'btn danger'));
3580
+ const actions = el('div', 'pactions');
3581
+ actions.appendChild(button('usar', (event) => { event.stopPropagation(); useProvider(p.id); }));
3582
+ actions.appendChild(button('editar', (event) => { event.stopPropagation(); openProviderForm(p); }));
3583
+ if (p.stored) actions.appendChild(button('borrar', (event) => { event.stopPropagation(); removeProvider(p.id); }, 'btn danger'));
3584
+ li.appendChild(actions);
3583
3585
  li.addEventListener('click', () => openProviderForm(p));
3584
3586
  providerListEl.appendChild(li);
3585
3587
  }
@@ -3779,9 +3781,11 @@ async function refreshMcp({ connect = false } = {}) {
3779
3781
  }
3780
3782
  main.append(title, el('div', 'pmeta', parts.join(' · ')));
3781
3783
  li.appendChild(main);
3782
- li.appendChild(button(s.enabled ? 'deshabilitar' : 'habilitar', (event) => { event.stopPropagation(); toggleMcp(s); }));
3783
- li.appendChild(button('editar', (event) => { event.stopPropagation(); openMcpForm(s); }));
3784
- if (s.source !== 'config') li.appendChild(button('borrar', (event) => { event.stopPropagation(); removeMcp(s.id); }, 'btn danger'));
3784
+ const actions = el('div', 'pactions');
3785
+ actions.appendChild(button(s.enabled ? 'deshabilitar' : 'habilitar', (event) => { event.stopPropagation(); toggleMcp(s); }));
3786
+ actions.appendChild(button('editar', (event) => { event.stopPropagation(); openMcpForm(s); }));
3787
+ if (s.source !== 'config') actions.appendChild(button('borrar', (event) => { event.stopPropagation(); removeMcp(s.id); }, 'btn danger'));
3788
+ li.appendChild(actions);
3785
3789
  li.addEventListener('click', () => openMcpForm(s));
3786
3790
  mcpListEl.appendChild(li);
3787
3791
  }
@@ -3895,9 +3899,11 @@ async function refreshSkills() {
3895
3899
  el('div', 'pmeta', s.description || s.when || '(sin descripción)')
3896
3900
  );
3897
3901
  li.appendChild(main);
3898
- li.appendChild(button(s.enabled ? 'deshabilitar' : 'habilitar', (event) => { event.stopPropagation(); toggleSkill(s.id); }));
3899
- li.appendChild(button('editar', (event) => { event.stopPropagation(); openSkillForm(s.id); }));
3900
- if (s.removable) li.appendChild(button('borrar', (event) => { event.stopPropagation(); removeSkill(s.id); }, 'btn danger'));
3902
+ const actions = el('div', 'pactions');
3903
+ actions.appendChild(button(s.enabled ? 'deshabilitar' : 'habilitar', (event) => { event.stopPropagation(); toggleSkill(s.id); }));
3904
+ actions.appendChild(button('editar', (event) => { event.stopPropagation(); openSkillForm(s.id); }));
3905
+ if (s.removable) actions.appendChild(button('borrar', (event) => { event.stopPropagation(); removeSkill(s.id); }, 'btn danger'));
3906
+ li.appendChild(actions);
3901
3907
  li.addEventListener('click', () => openSkillForm(s.id));
3902
3908
  skillListEl.appendChild(li);
3903
3909
  }
@@ -5173,9 +5179,11 @@ async function refreshProcs() {
5173
5179
  el('div', 'pmeta', `${proc.status}${proc.port ? ` · puerto ${proc.port}` : ''}`)
5174
5180
  );
5175
5181
  li.appendChild(main);
5176
- li.appendChild(button('logs', () => { selectedProc = proc.id; refreshProcLog(); }));
5177
- if (proc.port) li.appendChild(button('preview', () => { setExplorerMode('preview'); loadPreview(`http://127.0.0.1:${proc.port}`); }));
5178
- if (proc.running) li.appendChild(button('detener', () => stopProc(proc.id), 'btn danger'));
5182
+ const actions = el('div', 'pactions');
5183
+ actions.appendChild(button('logs', () => { selectedProc = proc.id; refreshProcLog(); }));
5184
+ if (proc.port) actions.appendChild(button('preview', () => { setExplorerMode('preview'); loadPreview(`http://127.0.0.1:${proc.port}`); }));
5185
+ if (proc.running) actions.appendChild(button('detener', () => stopProc(proc.id), 'btn danger'));
5186
+ li.appendChild(actions);
5179
5187
  procsListEl.appendChild(li);
5180
5188
  }
5181
5189
  }
@@ -37,7 +37,7 @@
37
37
  </head>
38
38
  <body>
39
39
  <header class="topbar">
40
- <div class="brand"><span class="logo"><img src="/icons/logo.png" alt="Tamnora" /></span> <span class="ver">v0.11.0</span></div>
40
+ <div class="brand"><span class="logo"><img src="/icons/logo.png" alt="Tamnora" /></span> <span class="ver">v0.11.2</span></div>
41
41
  <nav class="tabs">
42
42
  <button class="tab active" data-tab="chat">chat</button>
43
43
  <button class="tab" data-tab="providers">proveedores</button>
@@ -1220,6 +1220,16 @@ pre.code code { background: none; border: 0; padding: 0; font-size: 0.92em; }
1220
1220
  .providers .pid { font-weight: 700; }
1221
1221
  .providers .pmeta { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1222
1222
  .providers .grow { flex: 1; min-width: 0; }
1223
+ .providers .pactions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
1224
+ .providers li.muted {
1225
+ border: 0;
1226
+ background: none;
1227
+ backdrop-filter: none;
1228
+ box-shadow: none;
1229
+ cursor: default;
1230
+ padding-left: 2px;
1231
+ }
1232
+ .providers li.muted:hover { border: 0; }
1223
1233
 
1224
1234
  .summary { color: var(--muted); }
1225
1235
  .summary.ok { color: var(--ok); }
@@ -1598,17 +1608,38 @@ dialog.file-modal.preview-maximized {
1598
1608
  }
1599
1609
  .composer {
1600
1610
  margin: 8px 10px 6px;
1601
- padding: 8px 10px;
1611
+ padding: 9px 10px;
1602
1612
  border-radius: 18px;
1603
1613
  }
1614
+ /* En un movil el input flotaba sin fondo y casi no se notaba: lo sacamos a su
1615
+ propia linea, con campo visible y 16px (evita el zoom de iOS al enfocar). */
1616
+ .composer-row {
1617
+ flex-wrap: wrap;
1618
+ align-items: center;
1619
+ gap: 8px;
1620
+ }
1604
1621
  .composer textarea {
1622
+ order: -1;
1623
+ flex: 1 0 100%;
1605
1624
  max-height: 40vh;
1625
+ min-height: 46px;
1626
+ padding: 11px 13px;
1627
+ border: 1px solid var(--border);
1628
+ border-radius: 14px;
1629
+ background: var(--bg);
1630
+ font-size: 16px;
1631
+ line-height: 1.45;
1632
+ }
1633
+ .composer textarea:focus {
1634
+ border: 1px solid var(--accent);
1635
+ box-shadow: 0 0 0 4px var(--accent-soft);
1606
1636
  }
1607
1637
  .ctrl-pill {
1608
1638
  max-width: 46vw;
1609
1639
  }
1610
1640
  .model-pill {
1611
- max-width: 40vw;
1641
+ max-width: 46vw;
1642
+ margin-left: auto;
1612
1643
  }
1613
1644
  .composer-status {
1614
1645
  flex-wrap: wrap;
@@ -1617,6 +1648,26 @@ dialog.file-modal.preview-maximized {
1617
1648
  .composer-status .spacer {
1618
1649
  display: none;
1619
1650
  }
1651
+ /* MCP / skills / proveedores / procesos: el titulo se apretaba contra los
1652
+ botones y la descripcion quedaba cortada. Pasamos las acciones a su propia
1653
+ linea y dejamos que el texto respire. */
1654
+ .providers li {
1655
+ flex-wrap: wrap;
1656
+ align-items: flex-start;
1657
+ gap: 8px 10px;
1658
+ padding: 11px 12px;
1659
+ }
1660
+ .providers .pactions {
1661
+ flex: 0 0 100%;
1662
+ justify-content: flex-end;
1663
+ }
1664
+ .providers .grow .pmeta {
1665
+ white-space: normal;
1666
+ overflow: hidden;
1667
+ display: -webkit-box;
1668
+ -webkit-line-clamp: 2;
1669
+ -webkit-box-orient: vertical;
1670
+ }
1620
1671
  .empty-brand {
1621
1672
  font-size: clamp(30px, 12vw, 46px);
1622
1673
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danieltmn/tamnora",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "Harness local para IAs: agente CLI-first con TUI, consola web y evaluacion. Node puro, sin dependencias.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.25.0",