--- name: kanban lang: go domain: tools version: 0.3.0 description: "Kanban board con persistencia SQLite, drag-and-drop entre columnas (dnd-kit) y tracking del tiempo que cada tarjeta pasa en cada columna. Frontend Vite + React + Mantine v9 embebido en el binario Go." tags: [service, kanban, web, dnd-kit, mantine, sqlite, time-tracking] uses_functions: - random_hex_id_go_core - parse_date_or_default_go_core - sqlite_open_go_infra - sqlite_apply_migrations_go_infra - sqlite_column_exists_go_infra - spa_handler_go_infra - http_router_go_infra - http_serve_go_infra - http_middleware_chain_go_infra - http_cors_middleware_go_infra - http_logger_middleware_go_infra - http_json_response_go_infra - http_error_response_go_infra - http_parse_body_go_infra - http_session_cookie_middleware_go_infra - http_session_token_extract_go_infra - http_session_cookie_set_go_infra - http_session_cookie_clear_go_infra - password_hash_go_infra - password_verify_go_infra - session_create_go_infra - session_cleanup_go_infra - duration_stats_go_datascience - format_duration_ts_core - format_datetime_short_ts_core - string_hash_palette_ts_core - color_bg_ts_ui - color_border_ts_ui - color_swatch_ts_ui - fetch_json_ts_infra - claude_stream_go_core - mcp_server_stdio_go_infra - ws_upgrader_go_infra uses_types: - DurationStats_go_datascience framework: "net/http + vite + react + mantine + dnd-kit" entry_point: "backend/main.go" dir_path: "apps/kanban" service: port: 8095 health_endpoint: /api/board health_timeout_s: 3 systemd_unit: kanban.service systemd_scope: user restart_policy: always runtime: systemd-user pc_targets: - aurgi-pc is_local_only: false # Validacion end-to-end (fase 4 del bucle reactivo). Ver issue 0068. e2e_checks: - id: build_frontend cmd: "cd frontend && pnpm install --frozen-lockfile && pnpm build" timeout_s: 180 expect_exit: 0 - id: build_backend cmd: "CGO_ENABLED=1 go build -tags fts5 -o kanban ." timeout_s: 120 expect_exit: 0 - id: migrations_apply cmd: "rm -f /tmp/kanban_e2e.db && ./kanban --port 0 --db /tmp/kanban_e2e.db --migrate-only" timeout_s: 15 expect_exit: 0 - id: migrations_schema cmd: "sqlite3 /tmp/kanban_e2e.db 'SELECT version FROM schema_migrations ORDER BY version;'" expect_stdout_contains: "1" - id: smoke_api cmd: "./kanban --port 8195 --db /tmp/kanban_e2e.db &" health: "http://127.0.0.1:8195/api/board" timeout_s: 10 - id: tests_go cmd: "go test -tags fts5 -count=1 ./..." timeout_s: 120 expect_exit: 0 --- ## Arquitectura Single-binary: backend Go con frontend Vite embebido. SQLite local con tres tablas (`columns`, `cards`, `card_column_history`) y endpoints REST. ``` ./kanban --port 8095 --db kanban.db ``` ### Schema SQLite (`migrations/001_init.sql` … `010_card_messages.sql`) - **columns** — id, name, position, created_at - **cards** — id, title, description, column_id (FK), position, created_at, updated_at - **card_column_history** — id, card_id (FK), column_id, entered_at, exited_at - Una entrada con `exited_at IS NULL` = posicion actual - Al mover una tarjeta a otra columna: cierra la entrada activa (`exited_at = now`) e inserta una nueva - El borrado de tarjeta hace CASCADE sobre el historial - **card_messages** (migration 010) — id, card_id (FK CASCADE), author_id (nullable), body, created_at. Comentarios humano-a-humano por card; distintos de `card_events` (sistema) y `/api/chat` (LLM global). ### API REST | Metodo | Path | Cuerpo | |---|---|---| | GET | `/api/board` | — (retorna `{columns, cards}`, cada card incluye `time_in_column_ms`) | | POST | `/api/columns` | `{name}` | | PATCH | `/api/columns/{id}` | `{name?, position?}` | | DELETE | `/api/columns/{id}` | — (cascade a cards) | | POST | `/api/columns/reorder` | `{ids: [...]}` | | POST | `/api/cards` | `{column_id, title, description?}` | | PATCH | `/api/cards/{id}` | `{title?, description?}` | | DELETE | `/api/cards/{id}` | — | | POST | `/api/cards/{id}/move` | `{column_id, ordered_ids: [...]}` | | POST | `/api/cards/{id}/duplicate` | — (clona la card en la misma columna al final; copia titulo+" (copia)", descripcion, color, requester, assignee, tags, stickers, deadline; NO copia historial ni mensajes) | | GET | `/api/cards/{id}/messages` | — (lista de comentarios humano-a-humano de la card) | | POST | `/api/cards/{id}/messages` | `{body}` (crea comentario; author = usuario de la sesion) | | DELETE | `/api/cards/{cid}/messages/{mid}` | — (solo el autor puede borrar su mensaje) | | GET | `/api/cards/{id}/history` | — (timeline con duraciones por columna) | | GET | `/api/flags` | — (retorna `{ : bool }` con los feature flags efectivos en esta instancia) | | POST | `/api/auth/register` | `{username, password, display_name?}` (devuelve 403 `registration_disabled` si el flag `registration-enabled` esta en `false`) | ### Feature flags `dev/feature_flags.json` (lado del repo) define los flags por instancia. Se cargan al arrancar (override con `--flags `); fichero ausente equivale a "todos los flags en `false`". El endpoint `GET /api/flags` expone el estado actual para que el frontend oculte UI condicional (ej. el toggle de "Registrate" en `LoginPage` solo aparece cuando `registration-enabled` es `true`). | Flag | Default | Efecto cuando esta en `true` | |---|---|---| | `registration-enabled` | `false` | Permite crear cuentas nuevas via `POST /api/auth/register` y muestra el toggle "Registrate" en la pantalla de login. | ### Frontend - **dnd-kit** (`@dnd-kit/core` + `@dnd-kit/sortable`) para drag-and-drop entre y dentro de columnas (multi-container). - **Mantine v9** + `@tabler/icons-react` para UI. - **Modales** con `@mantine/modals` (confirmacion borrado, history timeline). - Time-in-column live: `time_in_column_ms` del backend + tick local cada segundo para que el badge se actualice sin reload. - DnD con `closestCorners` + `DragOverlay` para feedback visual al arrastrar. - **Auto-refresh:** el board se recarga cada 30s (`api.getBoard`) sin interaccion del usuario; equivalente a pulsar el boton de refresco. El tick de 1s del time-in-column es independiente y no toca red. - **Modal de card en dos columnas** (`CardEditPanel`): izquierda mantiene `CardForm` (titulo, solicitante, descripcion, asignacion, tags); derecha es un `Tabs` con `Chat` (por defecto) | `Enlaces` | `Archivos` (proximamente). Tamaño del modal: 85% del viewport. - **Chat per-card** (`CardChatPanel`): lista de comentarios humano-a-humano persistidos en `card_messages`. Enter envia, Shift+Enter salto de linea. Solo el autor puede borrar su propio mensaje. - **Enlaces** (`CardLinksPanel`): extrae URLs (`https?://...`) de titulo, descripcion y cuerpo de cada mensaje del chat. Deduplica, muestra hostname + URL completa + badge de origen. Click abre en pestaña nueva (`target="_blank"`). - **Duplicar card:** click derecho sobre la card abre el menu contextual (mismo que el boton `⋮`), donde aparece el item "Duplicar". Al pulsarlo invoca `POST /api/cards/{id}/duplicate`. La copia se inserta al final de la misma columna con titulo + " (copia)". - **Sesion obligatoria para chat:** `POST/DELETE /api/cards/{id}/messages` exige sesion activa (401 si falta). `author_id` siempre poblado; no hay comentarios anonimos. - **Archivos (proximamente):** blobs persistidos en SQLite (`card_attachments` con `BLOB`), no en filesystem. ### Build ```bash cd frontend && pnpm install && pnpm build cd .. && CGO_ENABLED=1 go build -tags fts5 -o kanban . ./kanban --port 8095 --db kanban.db # Browser: http://localhost:8095 ``` ### Dev (frontend con HMR contra backend) ```bash # Terminal 1 ./kanban --port 8095 --db kanban.db # Terminal 2 cd frontend && pnpm dev # Browser: http://localhost:5180 (vite proxy /api → 8095) ``` ## Notas - Puerto por defecto 8095. - DB por defecto `kanban.db` en cwd. - IDs de columnas y tarjetas: 16 chars hex (8 bytes random) via `random_hex_id_go_core`. - El historial conserva la cronologia exacta — incluso despues de cerrar y reabrir el server, los tiempos vivos siguen contando desde `entered_at`. - El borrado de columna hace CASCADE: las tarjetas se borran y su historial tambien. Si se quiere preservar el historial al borrar, deberia archivarse en lugar de borrar. ## Capability growth log Una linea por bump SemVer. Bump-type segun `.claude/commands/version.md`: - `major`: breaking observable (CLI args, schema BBDD propia, formato wire). - `minor`: feature aditiva (nuevo panel, endpoint, opcion). - `patch`: bugfix sin cambio observable. - v0.1.0 (2026-05-18) — baseline.