handleCreateCard only published board.invalidated, which is not in the
module event filter, so the dispatcher dropped it and jiraHandler.create
never ran. Newly created cards therefore never produced a Jira issue,
unlike moves (card.moved) and chat (message.created) which already synced.
Emit card.created after assignee/tags are applied so the synced issue
carries them. board.invalidated is kept for the SPA refetch path. No loop
risk (card.created fires only from the HTTP handler) and no double-create
(board.invalidated stays out of the filter).
UI:
- Menu avatar dropdown: 'Importar de Jira' -> 'Jira' (renombrado).
- ImportJiraModal.tsx eliminado. Sustituido por JiraModal.tsx con Mantine Tabs:
* 'Importar de Jira': UI heredada del modal anterior intacta.
* 'Comprobar columnas': nueva. Lista cards linked y muestra desincronizadas
(kanban col vs Jira status actual). Por cada row: kanban col + expected jira
status + jira status real. Checkbox multi-select + boton 'Sincronizar' que
empuja Jira al status correcto (kanban gana).
Backend:
- GET /api/jira/check-columns: walk cards.jira_key != ''. Por cada uno GET
/rest/api/3/issue/{key}?fields=status. Compara status real vs status_map.
Devuelve {rows[], total, mismatches, in_sync, status_map, reverse_map}.
- POST /api/jira/reconcile-columns {card_ids[], direction:'kanban-wins'}:
reusa jiraHandler.transitionToStatus para empujar cada issue al status del
status_map de su columna kanban actual + actualiza cards.jira_last_status.
- Helper listLinkedCardsForCheck en jira_import.go.
Direction='kanban-wins' default. Reverse direction (jira-wins) no soportado
por ahora: mover cards desde el server tiene efectos colaterales (eventos,
notificaciones, timers) que no quiero disparar masivos sin pensar.
Bug: handleMoveCard solo emitia board.invalidated. Dispatcher mapeaba a
update() (PUT summary/description/labels) NUNCA a transition(), asi que
mover una card en kanban no transicionaba su Jira issue de columna. Solo
los labels reflejaban el cambio.
Fix backend (handlers.go):
- handleMoveCard ahora lee column_id antes del MoveCard. Si la card crusa
columnas (prev != new) publica 'card.moved' antes de 'board.invalidated'.
El dispatcher reconoce 'card.moved' y ejecuta transition() -> Jira status
cambia + labels sincronizan.
- Reorder dentro de la misma columna sigue como antes: solo board.invalidated
para refetch del cliente sin tocar Jira.
- nuevo helper db.lookupCardColumnID(cardID).
UX frontend (JiraSyncIndicator):
- Polling adaptativo: 5s steady, 1s mientras inflight=true. El usuario VE
el yellow durante el sync.
- Listener de window CustomEvent 'kanban-card-moved' (cardId match) que
fuerza un refetch inmediato (~150ms) tras drop. App.tsx dispara el evento
tras api.moveCard resolve. Yellow visible casi instantaneo en lugar de
esperar al proximo tick steady.
Backend:
- migration 018: cards.jira_last_status / sync_at / error (estado persistido del ultimo
sync para render UI sin polling Jira).
- Dispatcher: sync.Map inflight para 'yellow' realtime + persistencia de exito/fallo
en cards tras cada dispatch attempt.
- GET /api/cards/{id}/jira-sync: devuelve {jira_key, last_status, last_sync_at,
last_error, inflight, issue_url} para el tooltip del indicador.
- GET /api/jira/issues: lista issues del board 33 con flag already_imported +
mapped_column_id (reverse status_map). Filtros include_imported, limit.
- POST /api/jira/import: multi-key. Cada issue -> CreateCard + setCardJiraKey +
seed jira_last_status. Cae en columna mapeada por status, o en fallback_column_id.
ADF de description extraido a texto plano.
Frontend:
- JiraSyncIndicator: dot gris/amarillo/verde/rojo bajo IconDotsVertical de cada card.
Mantine HoverCard con jira_key, status, last_sync, last_error, link 'Abrir en Jira'.
Poll cada 10s, refresh-tick opcional.
- KanbanCard: agrupa menu + indicator en Stack vertical (indicator debajo de los 3 dots).
- ImportJiraModal: modal admin con tabla de issues. Checkbox por fila, filtro por texto,
toggle 'mostrar ya importadas', Select de columna fallback. Tras import recarga board.
- App.tsx: nueva entrada de menu 'Importar de Jira' (admin) y ImportJiraModal mounted.
Backend tests siguen verdes (test mock cubre transitions endpoints).
Frontend pnpm build OK.
- migration 014_card_files: tabla con soft-delete + index activo
- handlers POST/GET/DELETE en backend/files.go
- routes /api/cards/{id}/files, /api/files/{id}
- limite 10MB, storage en uploads/<card_id>/<random>__<safe>
Anade tres capas sobre el reporte diario del issue 0093:
1) Bocadillo del agente: cuadro azul encima de "Tareas hechas" con un
resumen en lenguaje natural (max 4 frases) generado por claude -p
sobre el JSON del reporte. Botones Regenerar e icono Settings.
2) Settings del prompt: modal con textarea editable para el template
del agente (key=daily_report_prompt). Compartido por todos los
usuarios. Boton Restablecer por defecto.
3) PDF descargable: boton que abre ventana nueva con HTML imprimible
(estilo A4, KPIs filtrados, tabla con enlaces absolutos por card).
Permite compartir el listado de tareas hechas con los solicitantes.
Backend:
- Migration 013 anade tablas daily_summaries y settings; seed del
prompt por defecto en castellano.
- daily_summary.go con GetSetting/SetSetting, GetDailySummary/Upsert,
runClaudePrompt (envuelve claude -p) y GenerateDailySummary que
orquesta DailyReportFor + plantilla + claude + persist.
- Nuevos endpoints:
* GET /api/reports/daily/summary
* POST /api/reports/daily/summary
* GET /api/settings/{key}
* PUT /api/settings/{key}
Frontend:
- api.ts: getDailySummary, generateDailySummary, getSetting, setSetting.
- DailyReport.tsx: estado de summary, settingsOpen, promptDraft,
filterRequester, filterAssignee, filteredDoneCards, exportPDF.
- Bocadillo con IconSparkles + IconRefresh + IconSettings.
- Modal de prompt con Guardar/Cancelar/Reset.
- Filtros Select por solicitante y asignado encima de la tabla.
- exportPDF abre window.open con HTML self-contained que incluye
enlaces ${origin}/?card=${id} y window.print() automatico.
E2E nuevo (daily-summary-pdf.spec.ts): CRUD del setting, GET summary
shape, presencia del boton PDF/Settings/Regenerar en el modal. No
invoca claude real (binario externo, no disponible en CI).
Suite completa 11/11 pasa.
Adds a daily report dashboard accessible by clicking a day number in the
calendar view. Renders inside a full-width modal (90% width).
Backend (new file backend/reports.go):
- Type DailyReport with KPIs, rankings, done_cards list, reopened cards,
3-bucket stale list (7/14/30d), lead time avg+p50+p95, 24-hour
movement histogram, deadlines met/missed list, tag distribution and
archived count.
- DB.DailyReportFor(date, tz) uses Europe/Madrid by default; computes
[start,end) in local time, converts to UTC and queries:
* cards.completed_at in range -> done list
* card_events kind=created in range -> created counts
* card_column_history.entered_at in range -> moves + hourly
* previousColumnWasDone() -> reopened detection
* card_lock_history overlapping the day -> blocked_ms
* stale buckets: open history entries on non-done columns aged >=7d
- New route GET /api/reports/daily?date=YYYY-MM-DD&tz=Europe/Madrid.
Frontend:
- api.ts: DailyReport type + dailyReport(date, tz?) call.
- New component DailyReportView (components/DailyReport.tsx):
* 6 KPI cards (Hechas, Creadas, Movimientos, Bloqueado, Reabiertas,
Deadlines on-time %).
* 4 ranking cards (Top assignees done, Top assignees created,
Top requesters atendidas, Top requesters aportadas).
* Done cards table with click-to-jump (links open the card in board).
* Mantine BarChart with movements per hour.
* Tag chips, reopened list, deadlines list with late_ms, stale buckets.
- CalendarView wraps the day number in UnstyledButton with data-test
attribute and forwards onOpenDailyReport.
- App.handleOpenDailyReport opens modals.open size 90% with the view;
click on a card title closes the modal and jumps to the board with
highlight (reuses existing handleJumpToCard).
Tests (e2e/daily-report.spec.ts):
- Endpoint shape: kpis, done_cards, hourly_moves[24], stale buckets.
- Calendar day click opens the modal with "Reporte diario" title and
KPI labels visible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an archive layer separate from the trash. Cards in is_done columns
that have been there for more than 30 days are auto-archived on the next
board load (throttled to once every 30 minutes). Archived cards leave
the board but stay in the DB and are listed in a new sidebar drawer
"Hecho (archivo)" below the existing Papelera, with a one-click restore.
Schema (migration 012_card_archived.sql):
- ALTER TABLE cards ADD COLUMN archived_at TEXT;
- NULL = active, ISO timestamp = archived. Independent from deleted_at.
Backend:
- Card.ArchivedAt + JSON; ListCardsWithTime filters archived_at IS NULL.
- New methods: ArchiveCard, UnarchiveCard, ListArchivedCards,
AutoArchiveDoneOlderThan.
- New endpoints: GET /api/archive, POST /api/cards/:id/archive,
POST /api/cards/:id/unarchive.
- handleGetBoard invokes maybeAutoArchive (atomic throttle, 30 min sweep,
30 day cutoff). Errors logged but never block the board response.
Frontend:
- Card type + api.ts add the new field and helpers.
- App.tsx state for archive list, reload, archive/unarchive handlers.
- New sidebar drawer with toggle, count badge, restore button.
- KanbanCard gains an "Archivar" menu item (gated on isDone +
onArchive prop) for manual archiving of any done card.
Tests:
- Playwright e2e/archive.spec.ts: manual archive via menu, drawer
toggle, unarchive. Picks a done card via /api/board introspection so
it stays stable regardless of board state.
- Auto-archive of >30d cards: not under e2e (real time travel needed);
covered by code review of the SQL query and the throttle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds column-level max time limit. Cards whose time_in_column_ms exceeds
the limit show a red border + red halo. Columns marked as Done never
trigger the visual regardless of the limit (per spec).
Backend:
- Migration 011_column_max_time.sql adds columns.max_time_minutes
INTEGER NOT NULL DEFAULT 0 (0 = no limit). Aditiva, idempotente.
- Column struct + ColumnPatch + UpdateColumn handle the new field;
negatives clamp to 0; listing query includes it.
- handleUpdateColumn (PATCH /api/columns/:id) accepts max_time_minutes
in the JSON body.
Frontend:
- Column TS interface + UpdateColumnInput updated.
- KanbanColumn context menu: new entry "Tiempo maximo" using
window.prompt for low-friction config; shows current value when >0.
- KanbanCard receives columnOverdue prop calculated from the column
state and card.time_in_column_ms; renders red border (var
--mantine-color-red-6) with 2px width + 2px red halo when overdue.
- data-card-id, data-column-overdue, data-locked attributes on the card
paper element so e2e tests / scripts can query state.
Tests: TestColumnMaxTimeMinutes_Defaults + _Update verify the schema
default, the clamp on negative input, and that updating max_time leaves
other fields untouched.
Visual regression of the red border kept out of automated e2e because
it requires either clock control or real cards aged > N minutes; will be
verified manually after merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Backend: kanban binary gana subcomando `kanban mcp` que actua como MCP
server via stdio. Tools = mismo set que executeTool (14). El subprocess
llama de vuelta al backend via /api/tool/{name} con token interno.
- Backend: nuevo endpoint POST /api/tool/{name} (auth: X-Internal-Token).
- Backend: chat.go refactor — POST /api/chat reemplazado por GET
/api/chat/ws (WebSocket). Lanza claude -p con --output-format stream-json
--verbose --mcp-config y reenvia eventos (delta/tool_use/tool_result/
result/done/error) como mensajes JSON al cliente.
- Backend: usa funciones nuevas del registry claude_stream_go_core (spawn
+ parser NDJSON) y mcp_server_stdio_go_infra (JSON-RPC stdio).
- Frontend: streamChat sobre WebSocket. ChatPanel renderiza deltas en
vivo, chips para tool_use, badges teal/red para tool_result.
- Borrado: extractActions, actionsBlockMarker, XML system prompt.
- Tests: 7 nuevos en backend (chat_ws_test.go + endpoint /api/tool).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>