@luanpdd/kit-mcp 1.5.0 → 1.5.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
@@ -6,6 +6,38 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.5.2] - 2026-05-05
10
+
11
+ Patch de lifecycle: sidecar não desliga mais sozinho por idle.
12
+
13
+ ### Corrigido
14
+
15
+ - **Sidecar encerrava sozinho após 30min** mesmo com a aba aberta sem eventos. Default de `idleMs` mudou de `30 * 60 * 1000` (30min) para `0` (nunca encerra). Resolve "abro a sidecar pra acompanhar trabalho longo, saio almoçar, volto e tá morta". Quem quiser o comportamento antigo: `kit ui start --idle-ms 1800000`.
16
+
17
+ ### Sem mudanças de API
18
+
19
+ Patch isolado em `src/ui/server.js`. Stable API v1.0+ preservada.
20
+
21
+ ### Heads-up
22
+
23
+ Se você tem `@luanpdd/kit-mcp` instalado globalmente (`npm i -g`) e `kit ui start` está dando "unknown command 'ui'", a versão global está stale. Atualize com `npm i -g @luanpdd/kit-mcp@latest`.
24
+
25
+ ## [1.5.1] - 2026-05-05
26
+
27
+ Patch da UI sidecar: auto-reconnect quando o server reinicia + bordas com respiro.
28
+
29
+ ### Corrigido
30
+
31
+ - **UI fica presa em "desconectado" quando server volta.** O `EventSource` nativo às vezes estagna no estado `CONNECTING` mesmo depois do server voltar — usuário precisava recarregar a aba. Agora um poll do `/healthz` a cada 3s roda em paralelo: ao detectar 200, fecha o `EventSource` antigo, hidrata `/state`, e abre um novo. Funciona pra qualquer cenário (kill -9, `kit ui stop` + `kit ui start`, network blip, máquina suspended). Usuário **não precisa mais recarregar** — basta o server voltar.
32
+
33
+ - **Banner "Sidecar encerrou" persistia mesmo após reconnect.** Race entre o handler de shutdown e o poll de saúde podia deixar o banner visível mesmo com a conexão de volta. Agora `applyConnState("open")` sempre remove o banner — estado saudável significa que o aviso está stale.
34
+
35
+ - **Cropping nas bordas da timeline:** "há 22m" colado na borda esquerda e `runId`/tokens-chip cortados na direita. `.tl-row` ganhou `padding: var(--pad-tight) 12px`. `.tl-time` virou `padding-right: 8px`. `.tl-content` ganhou `padding-right: 4px` + `overflow: hidden`. Tokens-chip e tl-runid agora têm `flex-shrink: 0` explícito pra não encolher quando a mensagem ocupa muita largura.
36
+
37
+ ### Sem mudanças de API
38
+
39
+ Patch puro de UI. `src/ui/static/index.html` e `test/integration/ui-static.test.js` apenas. Stable API v1.0+ preservada.
40
+
9
41
  ## [1.5.0] - 2026-05-05
10
42
 
11
43
  UI sidecar — bug fixes visuais + tokens + histórico de sessão.
package/bin/ui.js CHANGED
@@ -33,7 +33,7 @@ if (args.help) {
33
33
  'Usage: node bin/ui.js [options]',
34
34
  ' --project-root <path> project root for lockfile keying (default: cwd)',
35
35
  ' --port <n> bind to a specific port (default: auto-pick 7100-7199)',
36
- ' --idle-ms <ms> idle shutdown timeout (default: 1800000 = 30min; 0 = never)',
36
+ ' --idle-ms <ms> idle shutdown timeout (default: 0 = never; pass e.g. 1800000 for 30min)',
37
37
  ' --version print version and exit',
38
38
  ' --help this text',
39
39
  '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luanpdd/kit-mcp",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Generic infrastructure to ship YOUR personal kit of agents/commands/skills as an MCP server, with cross-IDE sync (Claude Code, Cursor, Codex, Gemini, Windsurf, Antigravity, Copilot, Trae).",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.js CHANGED
@@ -379,7 +379,7 @@ ui.command('start')
379
379
  .description('Start the sidecar HTTP server in foreground (Ctrl+C to stop). Prints URL on stderr.')
380
380
  .option('--project-root <path>', 'Project root for lockfile keying (default: cwd)')
381
381
  .option('--port <n>', 'Bind to a specific port (default: auto-pick 7100-7199)')
382
- .option('--idle-ms <ms>', 'Idle shutdown timeout (default 30min; 0 = never)')
382
+ .option('--idle-ms <ms>', 'Idle shutdown timeout (default 0 = never; e.g. 1800000 for 30min)')
383
383
  .option('--no-open', 'Skip auto-opening the browser')
384
384
  .action(async (opts) => {
385
385
  const projectRoot = opts.projectRoot || process.cwd();
package/src/ui/server.js CHANGED
@@ -33,7 +33,7 @@ const HOST = '127.0.0.1';
33
33
  const HEARTBEAT_INTERVAL_MS = 15_000;
34
34
  const RING_BUFFER_SIZE = 200;
35
35
  const MAX_SSE_SUBSCRIBERS = 32;
36
- const DEFAULT_IDLE_MS = 30 * 60 * 1000; // 30 minutes
36
+ const DEFAULT_IDLE_MS = 0; // never auto-shutdown by default pass --idle-ms 1800000 to opt back in
37
37
 
38
38
  const SSE_HEADERS = {
39
39
  'Content-Type': 'text/event-stream; charset=utf-8',
@@ -483,9 +483,9 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
483
483
 
484
484
  .tl-row {
485
485
  display: grid;
486
- grid-template-columns: 64px 18px 1fr;
486
+ grid-template-columns: 64px 18px minmax(0, 1fr);
487
487
  gap: 0;
488
- padding: var(--pad-tight) 0;
488
+ padding: var(--pad-tight) 12px;
489
489
  position: relative;
490
490
  border-radius: 4px;
491
491
  transition: background .15s var(--ease);
@@ -514,8 +514,9 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
514
514
  font-size: 11px;
515
515
  color: var(--text-3);
516
516
  font-variant-numeric: tabular-nums;
517
- padding-left: 4px;
518
517
  padding-top: 1px;
518
+ padding-right: 8px;
519
+ text-align: left;
519
520
  }
520
521
 
521
522
  /* rail with node */
@@ -552,14 +553,17 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
552
553
  .tl-row[data-type="progress"] .tl-node { width: 5px; height: 5px; margin-top: 6px; }
553
554
 
554
555
  /* group runId connector — visually subtle indent */
555
- .tl-row[data-grouped="true"] { padding-left: 0; }
556
556
  .tl-row[data-grouped="true"] .tl-node { background: var(--text-3); }
557
557
 
558
558
  .tl-content {
559
559
  display: flex; align-items: baseline; gap: 8px;
560
560
  padding-left: 8px;
561
+ padding-right: 4px;
561
562
  min-width: 0;
563
+ overflow: hidden;
562
564
  }
565
+ .tl-content > .tokens-chip,
566
+ .tl-content > .tl-runid { flex-shrink: 0; }
563
567
 
564
568
  .tl-badge {
565
569
  font-family: var(--mono);
@@ -1846,6 +1850,12 @@ function applyConnState(s) {
1846
1850
  els.conn.dataset.state = "on";
1847
1851
  els.connLabel.textContent = "conectado";
1848
1852
  if (els.srcLabel) els.srcLabel.textContent = "ao vivo";
1853
+ // Healthy => banner stale. Independente de qual caminho colocou ele lá
1854
+ // (shutdown event antigo, race, hydrate trazendo shutdown histórico),
1855
+ // estar conectado significa que o aviso "Reabra com kit ui start" não
1856
+ // serve mais — remova sempre.
1857
+ const banner = document.getElementById("shutdown-banner");
1858
+ if (banner) banner.remove();
1849
1859
  } else if (s === "connecting") {
1850
1860
  els.conn.dataset.state = "off";
1851
1861
  els.connLabel.textContent = "conectando";
@@ -1893,11 +1903,16 @@ function connectRealSource() {
1893
1903
  evtSource.addEventListener("open", () => {
1894
1904
  lastConnectedAt = Date.now();
1895
1905
  applyConnState("open");
1906
+ stopHealthPoll(); // já tá conectado, não precisa pollar
1896
1907
  });
1897
1908
 
1898
1909
  evtSource.addEventListener("error", () => {
1899
- // EventSource auto-retries; reflect "closed" until next open fires.
1910
+ // EventSource auto-retries; nós atualizamos o estado visível e iniciamos
1911
+ // o poll-de-saúde paralelo, que cobre o caso "server reiniciou" — o native
1912
+ // retry às vezes gruda em CONNECTING sem nunca virar OPEN, e o poll detecta
1913
+ // quando o /healthz volta a responder e força um connectRealSource() limpo.
1900
1914
  applyConnState("closed");
1915
+ startHealthPoll();
1901
1916
  });
1902
1917
 
1903
1918
  // Listen for each typed event the server emits.
@@ -1907,6 +1922,9 @@ function connectRealSource() {
1907
1922
  if (data && data.type === "shutdown") {
1908
1923
  applyConnState("shutdown");
1909
1924
  showShutdownBanner();
1925
+ // Mesmo após shutdown, um novo `kit ui start` faz o server voltar.
1926
+ // Continue tentando reconectar — usuário não precisa recarregar a aba.
1927
+ startHealthPoll();
1910
1928
  }
1911
1929
  ingest(data);
1912
1930
  } catch (_) { /* swallow malformed */ }
@@ -1917,12 +1935,41 @@ function connectRealSource() {
1917
1935
  evtSource.onmessage = handler; // fallback for unnamed
1918
1936
  }
1919
1937
 
1920
- /* Background-tab recovery — Chrome throttles timers in inactive tabs and the
1921
- native EventSource retry can stall. When the tab becomes visible again and
1922
- we know we're closed, force a fresh hydrate + reconnect. */
1938
+ /* ───────────────────── health-poll auto-reconnect ─────────────────────
1939
+ Quando o EventSource cai e fica preso (server reiniciado, sidecar `kit ui
1940
+ stop` seguido de `kit ui start`, network blip), o native retry pode estagnar
1941
+ em CONNECTING. Esse poll roda em segundo plano em paralelo: quando o /healthz
1942
+ volta a responder 200, fechamos o EventSource antigo, hidratamos /state, e
1943
+ abrimos um novo. Para de pollar assim que conectamos com sucesso. */
1944
+ let healthPollTimer = null;
1945
+ function startHealthPoll() {
1946
+ if (healthPollTimer) return;
1947
+ healthPollTimer = setInterval(async () => {
1948
+ if (els.conn.dataset.state === "on") { stopHealthPoll(); return; }
1949
+ try {
1950
+ const res = await fetch("/healthz", { credentials: "omit", cache: "no-store" });
1951
+ if (res.ok) {
1952
+ stopHealthPoll();
1953
+ // Banner de shutdown é stale agora que o server voltou — remova.
1954
+ const banner = document.getElementById("shutdown-banner");
1955
+ if (banner) banner.remove();
1956
+ await hydrateFromState();
1957
+ connectRealSource();
1958
+ }
1959
+ } catch (_) { /* server ainda não voltou — keep tentando */ }
1960
+ }, 3000);
1961
+ }
1962
+ function stopHealthPoll() {
1963
+ if (healthPollTimer) { clearInterval(healthPollTimer); healthPollTimer = null; }
1964
+ }
1965
+
1966
+ /* Background-tab recovery — quando o tab volta a ficar visível e estamos fora
1967
+ do estado conectado, força um reconnect imediato em vez de esperar o próximo
1968
+ tick de health poll. */
1923
1969
  document.addEventListener("visibilitychange", () => {
1924
1970
  if (document.visibilityState !== "visible") return;
1925
- if (els.conn.dataset.state === "off") {
1971
+ if (els.conn.dataset.state !== "on") {
1972
+ stopHealthPoll();
1926
1973
  hydrateFromState().then(connectRealSource);
1927
1974
  }
1928
1975
  });