feinai 0.8.0 → 0.8.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
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
5
 
6
+ ## [0.8.2] - 2026-09-02
7
+
8
+ ### Fixed
9
+
10
+ - **Flicker en el dashboard mientras una página de spec/task detail estaba abierta.** `logRequest` sólo suprimía un listado exacto de paths (`/api/status`, `/api/specs`, ...); los reads con id (`/api/tasks/:id`, `/api/tasks/:id/worktree-status`) se colaban al log de requests. Cada uno disparaba el evento SSE `refresh`, que hacía que el dashboard se re-renderizara y re-fetchara la misma página, generando un nuevo request logueado — un loop autoalimentado de ~1.5s mientras esa página quedaba abierta. Ahora se suprime cualquier GET del actor `dashboard` (las mutaciones POST/PATCH/DELETE se siguen registrando y disparando refresh para el resto).
11
+
12
+ ## [0.8.1] - 2026-09-02
13
+
14
+ ### Added
15
+
16
+ - Chips de estado con contador en las tarjetas Specs y Tasks del overview. Navegan a la lista completa filtrada (`#/specs?status=pending`) en vez de filtrar la tarjeta truncada, para que el filtro nunca opere sobre un recorte arbitrario. Se omiten los estados sin items.
17
+
18
+ ### Fixed
19
+
20
+ - El contenedor del toast quedaba parcialmente visible en reposo: `translateY(140%)` sobre una caja de 30px la desplaza 42px, menos de los 54px necesarios para salir con `bottom: 24px`. Ahora usa `opacity` + `visibility`.
21
+
6
22
  ## [0.8.0] - 2026-09-02
7
23
 
8
24
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feinai",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Task & spec manager for AI agents — parallel worktrees, live dashboard, SDD skills",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  type OrphanKillResult,
58
58
  } from "./server-state";
59
59
 
60
- const VERSION = "0.8.0";
60
+ const VERSION = "0.8.2";
61
61
 
62
62
  interface ParsedArgs {
63
63
  positional: string[];
@@ -330,6 +330,7 @@
330
330
  font-family: inherit;
331
331
  }
332
332
  .filter-btn:hover { color: var(--fg); border-color: var(--fg-dim); }
333
+ .filter-count { margin-left: 6px; opacity: 0.7; font-variant-numeric: tabular-nums; }
333
334
  .filter-btn.active { background: var(--border); color: var(--fg); border-color: var(--border-strong); }
334
335
 
335
336
  .filter-btn[data-status="pending"] { border-color: var(--yellow); color: var(--yellow); }
@@ -461,10 +462,17 @@
461
462
  max-width: 360px;
462
463
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
463
464
  transform: translateY(140%);
464
- transition: transform 0.2s ease;
465
+ opacity: 0;
466
+ visibility: hidden;
467
+ transition: transform 0.2s ease, opacity 0.2s ease, visibility 0s linear 0.2s;
465
468
  z-index: 200;
466
469
  }
467
- .toast.visible { transform: translateY(0); }
470
+ .toast.visible {
471
+ transform: translateY(0);
472
+ opacity: 1;
473
+ visibility: visible;
474
+ transition: transform 0.2s ease, opacity 0.2s ease, visibility 0s;
475
+ }
468
476
  .toast.error { border-left-color: var(--red); }
469
477
  .toast-title { font-weight: 600; margin-bottom: 4px; }
470
478
  .toast-body { color: var(--fg-dim); font-size: 12px; }
@@ -916,6 +924,18 @@
916
924
  return `<div class="filter-bar">${btns.join("")}</div>`;
917
925
  }
918
926
 
927
+ // Overview chips: navigate to the full filtered list instead of filtering
928
+ // the truncated card, so a filter never operates on an arbitrary slice.
929
+ // Statuses with no items are omitted — a chip leading nowhere is a dead end.
930
+ function filterLinks(basePath, statuses, items) {
931
+ const chips = statuses
932
+ .map((s) => ({ s, n: items.filter((it) => it.status === s).length }))
933
+ .filter(({ n }) => n > 0)
934
+ .map(({ s, n }) =>
935
+ `<button class="filter-btn" data-status="${s}" data-goto="${escapeHtml(basePath)}?status=${s}" title="View all ${statusLabel(s)}">${escapeHtml(statusLabel(s))}<span class="filter-count">${n}</span></button>`);
936
+ return chips.length > 1 ? `<div class="filter-bar">${chips.join("")}</div>` : "";
937
+ }
938
+
919
939
  // ================= Event rendering =================
920
940
  function eventRow(e) {
921
941
  if (e._kind === "request") {
@@ -1003,6 +1023,7 @@
1003
1023
  <h2>Specs <span class="count">${specs.length}</span>
1004
1024
  <a class="btn sm" href="#/new/spec" style="margin-left:auto;">New spec</a>
1005
1025
  </h2>
1026
+ ${filterLinks("#/specs", ["pending", "in_progress", "completed", "archived"], specs)}
1006
1027
  ${listBlock(specs, specItem, {
1007
1028
  limit: OVERVIEW_LIMIT,
1008
1029
  moreHref: "#/specs",
@@ -1016,6 +1037,7 @@
1016
1037
  <h2>Tasks <span class="count">${tasks.length}</span>
1017
1038
  <a class="btn sm" href="#/new/task" style="margin-left:auto;">New task</a>
1018
1039
  </h2>
1040
+ ${filterLinks("#/tasks", ["pending", "in_progress", "completed", "failed"], tasks)}
1019
1041
  ${listBlock(tasks, taskItem, {
1020
1042
  limit: OVERVIEW_LIMIT,
1021
1043
  moreHref: "#/tasks",
package/src/server.ts CHANGED
@@ -163,10 +163,15 @@ function deriveSource(actor: string): "Dashboard" | "API" | "Terminal" {
163
163
  function logRequest(method: string, path: string, actor: string): void {
164
164
  if (path === "/" || path === "/api/events/stream") return;
165
165
 
166
- // Suppress dashboard's own periodic polling noise only.
167
- // Everything else (agent reads, terminal commands, dashboard detail views) is logged.
168
- const POLL_PATHS = ["/api/status", "/api/specs", "/api/tasks", "/api/events", "/api/agents"];
169
- if (actor === "dashboard" && POLL_PATHS.includes(path)) return;
166
+ // Suppress dashboard's own reads (list views, detail views, worktree-status
167
+ // polling). The exact-match list missed per-id paths like /api/tasks/:id and
168
+ // /api/tasks/:id/worktree-status those got logged, which fed the SSE
169
+ // "refresh" broadcast, which made every open dashboard re-render and re-fetch,
170
+ // logging a new request that fed the next refresh: a self-sustaining loop that
171
+ // caused the whole page to flicker every ~1.5s while a task/spec detail page
172
+ // was open. Dashboard mutations (POST/PATCH/DELETE) are never GET, so they
173
+ // still get logged and still drive refresh for everyone else.
174
+ if (actor === "dashboard" && method === "GET") return;
170
175
 
171
176
  const source = deriveSource(actor);
172
177
  requestLog.push({