chore: avance acumulado de sesiones previas (reorg dev/issues + ajustes)

Reorganizacion de dev/issues en subcarpetas (completed/, cpp/, gamedev/,
kanban/, trading/, imagegen/, matrix/) y cambios acumulados en cmd/fn/pyrunner,
.claude/commands y settings. Trabajo de otro LLM/sesion, commiteado a peticion
del usuario para desbloquear el working tree. Excluido logs/ardour_mcp_server.log (ruido).
This commit is contained in:
2026-06-30 14:43:51 +02:00
parent 5501507588
commit a3f75d61ec
125 changed files with 421 additions and 203 deletions
@@ -0,0 +1,187 @@
---
id: "0027"
title: "C++ gl_compute_shader + gl_pingpong_fbo + DAG node Compute"
status: pendiente
type: feature
domain:
- cpp-stack
scope: multi-app
priority: alta
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0027 — C++ gl_compute_shader + gl_pingpong_fbo + DAG node Compute
## APP Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0027 |
| **Estado** | pendiente |
| **Prioridad** | alta |
| **Tipo** | feature — C++ gfx (cpp/functions/gfx) |
## Dependencias
Recomendado leer primero `gl_loader_cpp_gfx`, `gl_shader_cpp_gfx`, `gl_framebuffer_cpp_gfx`, `dag_catalog_cpp_gfx`, `dag_compile_cpp_gfx`. Sin bloqueos.
**Desbloquea:** simulaciones GPGPU (particulas, fluidos), feedback loops (reaction-diffusion, blur multipass, bloom), cualquier shader que necesite estado entre frames. Triplica el alcance del DAG visual de `shaders_lab`.
---
## Objetivo
Tres adiciones complementarias:
1. **`gl_compute_shader_cpp_gfx`** — carga + dispatch de compute shaders (GLSL `#version 430 core` + `layout(local_size_*) in`). API parejo a `gl_shader`.
2. **`gl_pingpong_fbo_cpp_gfx`** — wrapper de **dos** `gl_framebuffer` con swap A↔B en cada paso. Permite usar el output del frame N-1 como input del frame N (feedback).
3. **DAG kind `Compute`** — nuevo `DagNodeKind::Compute` en `dag_catalog`/`dag_compile`. Nodo que ejecuta un compute shader sobre la imagen actual, con N iteraciones configurable.
Demo en `primitives_gallery`: reaction-diffusion (Gray-Scott) usando ping-pong FBO + compute shader.
## Contexto
`gl_shader` solo soporta fragment shaders. El DAG actual es estrictamente fragment-pipeline (gen → op → blend). No hay forma de:
- Persistir estado entre frames (necesario para feedback effects).
- Hacer GPGPU verdadero (compute con buffers / images).
- Implementar efectos como fluid sim, particulas o blur multipass eficientes.
OpenGL compute shaders requieren `#version 430 core` (= GL 4.3). El proyecto ya pide GL 3.3+; subir el minimo solo para los nodos compute es aceptable (graceful degrade si la GPU no lo soporta).
## Arquitectura
```
cpp/functions/gfx/
├── gl_compute_shader.h # NEW
├── gl_compute_shader.cpp # NEW
├── gl_compute_shader.md # NEW
├── gl_pingpong_fbo.h # NEW
├── gl_pingpong_fbo.cpp # NEW
├── gl_pingpong_fbo.md # NEW
├── dag_types.h # MOD: añadir DagNodeKind::Compute
├── dag_catalog.cpp/.h # MOD: registrar nodos compute (ej: ReactionDiffusion)
├── dag_compile.cpp/.h # MOD: emitir paso compute en pipeline
└── dag_uniforms.cpp/.h # MOD si los compute tienen params
cpp/apps/primitives_gallery/
├── demos_compute.cpp # NEW
├── demos.h # MOD
├── main.cpp # MOD
└── CMakeLists.txt # MOD
```
### Pure core / impure shell
- `gl_compute_shader` y `gl_pingpong_fbo`: ambos `purity: impure`, `kind: function`.
- `dag_catalog`/`dag_compile`: ya son `pure`, mantener.
- `dag_node_editor` / `dag_panel`: ya son `impure`, no se tocan en este issue (los nodos compute aparecen automaticamente al añadirse al catalog).
### API propuesta
```cpp
namespace fn {
// gl_compute_shader.h
struct GlComputeProgram {
GLuint program = 0;
int local_x = 1, local_y = 1, local_z = 1;
bool ok() const { return program != 0; }
};
GlComputeProgram gl_compute_compile(const char* glsl_body); // body sin version
void gl_compute_destroy(GlComputeProgram&);
// Dispatch: bind images, set uniforms (callback opcional), glDispatchCompute, barrier.
void gl_compute_dispatch(const GlComputeProgram&, int groups_x, int groups_y, int groups_z = 1);
void gl_compute_bind_image(int unit, GLuint texture, GLenum access /*GL_READ_ONLY|WRITE|READ_WRITE*/, GLenum format = GL_RGBA8);
const char* gl_compute_last_error();
// gl_pingpong_fbo.h — dos FBO RGBA8 del mismo tamaño con A/B swap.
struct PingpongFbo {
Framebuffer a, b;
bool a_is_front = true; // front = ultimo escrito
};
PingpongFbo pingpong_create(int w, int h);
void pingpong_destroy(PingpongFbo&);
void pingpong_resize(PingpongFbo&, int w, int h);
void pingpong_swap(PingpongFbo&);
const Framebuffer& pingpong_front(const PingpongFbo&); // ultima lectura
const Framebuffer& pingpong_back (const PingpongFbo&); // siguiente write target
}
```
### DAG: nuevo kind Compute
`dag_catalog` registra al menos un nodo de ejemplo: `compute_blur_separable` o `compute_reaction_diffusion`. Tras este issue queda abierto añadir mas (cada uno = un nuevo `DagNode`).
`dag_compile`: cuando emite un paso `Compute`, decide bindings (image read = ping-front, image write = pong-back) y devuelve metadatos para que el host haga `dispatch + swap` correctamente.
## Tareas
### Fase 1 — gl_compute_shader
- 1.1 Implementar wrapper minimo. Detectar version GL >= 4.3 al inicializar; si no, retornar `program == 0` y guardar error "compute shaders require OpenGL 4.3+".
- 1.2 Helper `gl_compute_bind_image` con `glBindImageTexture`.
- 1.3 `.md` con frontmatter (`purity: impure`, `kind: function`, `error_type`).
### Fase 2 — gl_pingpong_fbo
- 2.1 Implementar wrapper que reusa el `gl_framebuffer` existente. Resize propaga a los dos.
- 2.2 `.md` con frontmatter.
### Fase 3 — DAG Compute kind
- 3.1 Añadir `DagNodeKind::Compute` en `dag_types.h` + serializacion (json IO).
- 3.2 `dag_catalog`: registrar nodo de ejemplo `compute_blur_2pass` (separable Gaussian blur, 2 dispatches via flag uniform `direction`).
- 3.3 `dag_compile`: emitir step compute con suficiente metadata para que el host (un nuevo helper) haga el dispatch.
- 3.4 Helper `dag_compute_run_step()` — recibe `DagStep` compute + `PingpongFbo` + uniforms y hace `dispatch + swap`.
### Fase 4 — Gallery demo
- 4.1 `demos_compute.cpp` con `demo_compute_reaction_diffusion()`: `PingpongFbo` 512×512 + compute shader Gray-Scott con sliders (feed, kill, dt). Usa `ImGui::Image` para mostrar el front buffer cada frame. Boton "reset" rellena con seed.
- 4.2 Registrar en gallery.
### Fase 5 — Tests + docs
- 5.1 Test (Linux + GPU disponible, opcional via env var `FN_GPU_TESTS=1`): compila un compute simple que escribe `vec4(1,0,0,1)` y verifica via `glReadPixels`.
- 5.2 Test puro de `dag_compile` con un pipeline que mezcla nodos `Gen` + `Compute`.
- 5.3 `./fn index` + `./fn show *_cpp_gfx` para los nuevos.
## Ejemplo de uso
```cpp
auto cs = fn::gl_compute_compile(R"glsl(
layout(local_size_x=8, local_size_y=8) in;
layout(rgba8, binding=0) uniform image2D src;
layout(rgba8, binding=1) uniform image2D dst;
void main() {
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
vec4 c = imageLoad(src, p);
imageStore(dst, p, vec4(1.0 - c.rgb, 1.0));
}
)glsl");
auto pp = fn::pingpong_create(512, 512);
glUseProgram(cs.program);
fn::gl_compute_bind_image(0, fn::pingpong_front(pp).color_tex, GL_READ_ONLY);
fn::gl_compute_bind_image(1, fn::pingpong_back (pp).color_tex, GL_WRITE_ONLY);
fn::gl_compute_dispatch(cs, 64, 64);
fn::pingpong_swap(pp);
```
## Decisiones de diseño
- **Subir minimo a GL 4.3 solo para nodos Compute** — el resto del pipeline sigue compilando con 3.3. `gl_compute_compile` falla con error claro en GPUs viejas.
- **Ping-pong fuera de FBO** — wrapper aparte, no fusionado con `gl_framebuffer` (mantenemos primitivos atomicos).
- **Catalog del DAG abierto** — un solo nodo compute en este issue. Otros (fluid, particles) se añaden en issues posteriores reusando los wrappers.
## Riesgos
- **macOS no soporta compute shaders en GL 4.1** — documentar limitacion. En macOS el path Compute queda inactivo.
- **Memory barriers**: olvidar `glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)` produce datos basura. Encapsular en `gl_compute_dispatch`.
- **Dispatch grande bloquea UI**: documentar que dispatches > 1M pixels deben dividirse o bajar local_size.
+187
View File
@@ -0,0 +1,187 @@
---
id: "0030"
title: "C++ audio reactivo (capture + FFT + uniform feed + viz)"
status: pendiente
type: feature
domain:
- cpp-stack
- frontend
scope: multi-app
priority: media
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0030 — C++ audio reactivo (capture + FFT + uniform feed + viz)
## APP Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0030 |
| **Estado** | pendiente |
| **Prioridad** | media |
| **Tipo** | feature — C++ multi-domain (core, gfx, viz) |
## Dependencias
`gl_loader_cpp_gfx`, `time_series_buffer_cpp_core`. Independiente de los demas issues.
**Desbloquea:** shaders audio-reactivos en `shaders_lab`, dashboards en vivo con waveform/spectrum, debugging visual de pipelines de audio.
---
## Objetivo
Cinco primitivos coherentes:
1. **`audio_capture_cpp_core`** — input mic/loopback con [miniaudio](https://github.com/mackron/miniaudio) (single-header, dominio publico). Lock-free ring buffer.
2. **`audio_fft_cpp_core`** — FFT real → magnitudes + smoothing (puro). Tamaño configurable (256/512/1024/2048).
3. **`audio_uniform_feed_cpp_gfx`** — sube espectro como `sampler1D` o `texture2D 1×N` al shader activo.
4. **`waveform_view_cpp_viz`** — dibuja muestras crudas en un line plot ImPlot.
5. **`spectrum_view_cpp_viz`** — dibuja magnitudes FFT en bar plot logaritmico.
Demo en `primitives_gallery`: input live → fft → vis dual + opcion de bind a un shader fullscreen reactivo.
## Contexto
`shaders_lab` no tiene input externo dinamico. Los visuals reaccionan a `u_time` y `u_mouse` solamente. Audio reactivo es uno de los casos mas comunes de uso creativo de shaders y abre demos espectaculares con muy poco codigo añadido.
## Arquitectura
```
cpp/
├── vendor/miniaudio/
│ └── miniaudio.h # NEW (~80k LOC, public domain)
├── functions/core/
│ ├── audio_capture.h/.cpp/.md # NEW (impure)
│ └── audio_fft.h/.cpp/.md # NEW (pure)
├── functions/gfx/
│ └── audio_uniform_feed.h/.cpp/.md # NEW (impure)
└── functions/viz/
├── waveform_view.h/.cpp/.md # NEW (pure component)
└── spectrum_view.h/.cpp/.md # NEW (pure component)
cpp/apps/primitives_gallery/
├── demos_audio.cpp # NEW
├── demos.h # MOD
├── main.cpp # MOD
└── CMakeLists.txt # MOD
cpp/CMakeLists.txt # MOD
```
### API propuesta
```cpp
namespace fn {
// audio_capture (impure)
struct AudioCapture;
struct AudioCaptureConfig {
int sample_rate = 48000;
int channels = 1; // mixdown a mono
int buffer_samples = 8192; // ring buffer
bool loopback = false; // Win/Linux pulse: capturar output del sistema
};
AudioCapture* audio_capture_start(const AudioCaptureConfig&);
void audio_capture_stop(AudioCapture*);
// Drena hasta `out_capacity` muestras float [-1,1]. Retorna count efectivo.
int audio_capture_read(AudioCapture*, float* out, int out_capacity);
const char* audio_capture_last_error();
// audio_fft (pure)
struct FftResult {
std::vector<float> magnitudes; // size = fft_size/2
float dc = 0.f;
float peak_freq_hz = 0.f;
};
FftResult audio_fft_compute(const float* samples, int n, int sample_rate, float smoothing = 0.7f, FftResult* prev = nullptr);
// audio_uniform_feed (impure)
struct AudioTexture { GLuint tex = 0; int n = 0; };
AudioTexture audio_texture_create(int n);
void audio_texture_destroy(AudioTexture&);
void audio_texture_upload(AudioTexture&, const float* magnitudes, int n);
void audio_texture_bind_uniform(GLuint program, const char* name, const AudioTexture&, int unit);
// waveform_view (pure component)
void waveform_view(const char* id, const float* samples, int n, ImVec2 size = {-1, 100});
// spectrum_view (pure component)
void spectrum_view(const char* id, const float* magnitudes, int n, int sample_rate, ImVec2 size = {-1, 150});
}
```
## Tareas
### Fase 1 — Vendor
- 1.1 `cpp/vendor/miniaudio/miniaudio.h` (pin commit). Crear `cpp/vendor/miniaudio/miniaudio_impl.cpp` con `#define MINIAUDIO_IMPLEMENTATION`.
- 1.2 Añadir source al CMakeLists.
### Fase 2 — audio_capture
- 2.1 Implementar wrapper. Backend default por OS (alsa/pulse en Linux, wasapi en Win). Lock-free ring buffer (single-producer/single-consumer) accumulando muestras del callback.
- 2.2 `loopback`: Linux pulse loopback monitor source; Windows wasapi loopback. Documentar limitaciones.
- 2.3 `.md` (`kind: function`, `purity: impure`, `error_type`).
### Fase 3 — audio_fft (puro)
- 3.1 Usar implementacion FFT minima (Cooley-Tukey radix-2) o vendorear [pffft](https://github.com/marton78/pffft) (BSD). Decidir y documentar.
- 3.2 Aplicar Hann window antes de FFT.
- 3.3 Smoothing exponencial frame-a-frame con `prev`.
- 3.4 Tests con seno puro a frecuencia conocida; `peak_freq_hz` debe coincidir.
- 3.5 `.md`.
### Fase 4 — audio_uniform_feed
- 4.1 Crear textura 1D (o 2D 1×N) `GL_R32F`. Subida con `glTexSubImage1D`/`2D`.
- 4.2 `.md`.
### Fase 5 — waveform_view + spectrum_view
- 5.1 `waveform_view`: linea con `ImPlot::PlotLine`. Eje y fijado [-1, 1]. Eje x = sample index.
- 5.2 `spectrum_view`: bar plot con eje x logaritmico (Hz), eje y dB (20*log10(magnitude+eps)).
- 5.3 `.md` para cada uno.
### Fase 6 — Gallery demo
- 6.1 `demos_audio.cpp` con `demo_audio_reactive()`: start capture, fft cada frame, dos vistas (waveform + spectrum), shader fullscreen 256×256 al lado que samplea la `audio_texture` y desplaza colores en funcion del bin medio.
- 6.2 Boton start/stop. Mostrar device en uso.
### Fase 7 — Tests + docs
- 7.1 Test FFT con seno conocido.
- 7.2 Test ring buffer (productor escribe N, consumidor lee, sin perdidas).
- 7.3 Smoke test: capture 100ms, asserts `read > 0`.
- 7.4 `./fn index` + `./fn show` de los 5.
## Ejemplo de uso
```cpp
auto* cap = fn::audio_capture_start({});
fn::FftResult prev{};
fn::run_app("audio", [&]{
float buf[2048];
int n = fn::audio_capture_read(cap, buf, 2048);
if (n >= 1024) {
prev = fn::audio_fft_compute(buf, 1024, 48000, 0.7f, &prev);
fn::waveform_view("##wav", buf, n);
fn::spectrum_view("##spec", prev.magnitudes.data(), prev.magnitudes.size(), 48000);
}
});
```
## Decisiones de diseño
- **miniaudio** vs portaudio: miniaudio es header-only, sin build extra; gana por simplicidad de integracion.
- **FFT minimo propio o pffft**: empezar con propio (50 LOC) — performance suficiente para 1024 samples a 60Hz. Si hace falta velocidad, swap por pffft.
- **`spectrum_view` y `waveform_view` puros**: no tocan el audio, solo dibujan.
## Riesgos
- **Permisos mic en macOS**: documentar Info.plist NSMicrophoneUsageDescription si se distribuye.
- **Loopback no estandar entre OSes**: documentar plataformas soportadas y degradar grácilmente si no esta disponible.
- **Latencia**: ring buffer vs callback puede acumular. Documentar tamaño y politica de drop.
@@ -0,0 +1,160 @@
---
id: "0033"
title: "C++ http_inspector + websocket_client"
status: pendiente
type: feature
domain:
- cpp-stack
scope: multi-app
priority: baja
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: [ausente-ready]
---
# 0033 — C++ http_inspector + websocket_client
## APP Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0033 |
| **Estado** | pendiente |
| **Prioridad** | baja |
| **Tipo** | feature — C++ core (cpp/functions/core) |
## Dependencias
`text_editor_cpp_core` (issue 0025) recomendado para editar bodies; fallback a `InputTextMultiline`. `time_series_buffer_cpp_core`.
**Desbloquea:** debugging visual de APIs internas y feeds en tiempo real desde C++ apps. Util para apps de monitoring que se conectan a servicios propios.
---
## Objetivo
Dos componentes complementarios:
1. **`http_inspector_cpp_core`** — panel ImGui tipo Postman minimo: input URL, dropdown method (GET/POST/PUT/DELETE/PATCH), headers (key/value list), body, boton Send. Muestra status, headers de respuesta, body con pretty-print JSON.
2. **`websocket_client_cpp_core`** — panel WebSocket: input ws://… , botones Connect/Disconnect, area de mensajes (timeline), input para enviar texto, contador msg/s y plot rate.
Vendorea [cpp-httplib](https://github.com/yhirose/cpp-httplib) (MIT, header-only) para HTTP. Para WS usa la API client de cpp-httplib (>=0.20 trae soporte ws basico) o vendorea [websocketpp](https://github.com/zaphoyd/websocketpp). Decidir en Fase 1.
Demo en `primitives_gallery` con un endpoint HTTP de prueba (httpbin.org) y un WS echo (`wss://echo.websocket.events`).
## Contexto
Apps C++ que consumen APIs internas (registry_api, deploy_server) suelen requerir scripts curl/Postman aparte para debugging. Tener un inspector embebido reduce friccion.
## Arquitectura
```
cpp/
├── vendor/cpp-httplib/
│ └── httplib.h # NEW (header-only, MIT)
├── functions/core/
│ ├── http_inspector.h/.cpp/.md # NEW (impure component)
│ └── websocket_client.h/.cpp/.md # NEW (impure component)
└── apps/primitives_gallery/
├── demos_net.cpp # NEW
├── demos.h # MOD
├── main.cpp # MOD
└── CMakeLists.txt # MOD
cpp/CMakeLists.txt # MOD
```
### API propuesta
```cpp
namespace fn {
struct HttpHeader { std::string key, value; };
struct HttpInspectorState {
std::string url = "https://httpbin.org/get";
std::string method = "GET";
std::vector<HttpHeader> headers;
std::string body;
int last_status = 0;
std::vector<HttpHeader> last_headers;
std::string last_body;
double last_ms = 0.0;
bool in_flight = false; // request en background
std::string error;
};
void http_inspector(const char* id, HttpInspectorState&, ImVec2 size = {-1, -1});
struct WsMessage { double t; bool incoming; std::string text; };
struct WebSocketClientState {
std::string url = "wss://echo.websocket.events";
bool connected = false;
std::string send_buf;
std::vector<WsMessage> messages; // capped at 500
float rate_msgs_s = 0.f;
std::string error;
};
void websocket_client(const char* id, WebSocketClientState&, ImVec2 size = {-1, -1});
}
```
## Tareas
### Fase 1 — Vendor + decision WS
- 1.1 Vendorear `httplib.h` (pinear version reciente con TLS opcional).
- 1.2 Decidir cliente WS:
- Opcion A: cpp-httplib `WebSocketClient` si la version vendoreada lo trae.
- Opcion B: vendorear `websocketpp` (header-only-ish, depende de Asio header-only).
- 1.3 Documentar la decision en `cpp/vendor/<lib>/README.md`.
### Fase 2 — http_inspector
- 2.1 Implementar el componente. Request en background con `std::thread` (ver `process_runner_cpp_core` como patrón).
- 2.2 Pretty-print: si `Content-Type: application/json`, formatear con un mini parser/printer JSON (o copiar uno minimal; no necesitamos validacion exhaustiva).
- 2.3 `.md` con frontmatter (`kind: component`, `purity: impure`, `error_type`).
### Fase 3 — websocket_client
- 3.1 Implementar `connect/disconnect` en thread; lockear cola de mensajes con mutex; copiar a `state.messages` en `websocket_client()` (main thread).
- 3.2 Calcular `rate_msgs_s` como contador por segundo con ventana deslizante (puede usar `time_series_buffer_cpp_core`).
- 3.3 `.md`.
### Fase 4 — Gallery demo
- 4.1 `demos_net.cpp` con `demo_http_inspector()` y `demo_websocket_client()`.
- 4.2 Registrar.
### Fase 5 — Tests + docs
- 5.1 Test pretty-print JSON sobre payload conocido.
- 5.2 Test ratelimit del rate_msgs_s.
- 5.3 Smoke test (opcional, requires net): GET a 127.0.0.1 contra un servidor levantado en el test.
- 5.4 `./fn index` + `./fn show`.
## Ejemplo de uso
```cpp
fn::HttpInspectorState http;
fn::WebSocketClientState ws;
fn::run_app("net", [&]{
if (ImGui::BeginTabBar("net")) {
if (ImGui::BeginTabItem("HTTP")) { fn::http_inspector("##h", http); ImGui::EndTabItem(); }
if (ImGui::BeginTabItem("WS")) { fn::websocket_client("##w", ws); ImGui::EndTabItem(); }
ImGui::EndTabBar();
}
});
```
## Decisiones de diseño
- **cpp-httplib** vs libcurl: header-only gana en complejidad de build. TLS via OpenSSL si esta disponible; degradar a HTTP si no.
- **Mensajes WS capped**: 500 maximo en buffer para no crecer indefinidamente; descartar oldest.
- **Sin authentication helpers**: el header `Authorization` se mete a mano. UX simple.
## Riesgos
- **TLS**: cpp-httplib requiere OpenSSL para `https://`. Documentar en CMake como opt-in.
- **WS reconnect**: no implementar auto-reconnect en MVP. El usuario reconecta manualmente.
- **Threading**: cuidado con marshalling de strings entre thread y UI. Mutex obligatorio en mensajes.
+138
View File
@@ -0,0 +1,138 @@
---
id: "0035"
title: "C++ map_tiles (slippy map OSM)"
status: pendiente
type: feature
domain:
- cpp-stack
scope: multi-app
priority: baja
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0035 — C++ map_tiles (slippy map OSM)
## APP Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0035 |
| **Estado** | pendiente |
| **Prioridad** | baja |
| **Tipo** | feature — C++ viz (cpp/functions/viz) |
## Dependencias
`gl_texture_load_cpp_gfx` (issue 0026), `gl_loader_cpp_gfx`. Vendor: `cpp-httplib` (si ya esta vendoreado por 0033) o reusar.
**Desbloquea:** apps GIS minimas, dashboards con localizacion (entities con lat/lon), debugging de scrapers geograficos.
---
## Objetivo
Componente ImGui que renderiza un slippy map estilo Leaflet sobre OpenStreetMap tiles, con pan/zoom y soporte para markers + polylines.
## Contexto
Hay datos geograficos en varios proyectos (registry de proveedores, viajes, infrastructura). No hay forma de visualizarlos en C++; se exporta a una notebook con folium o se monta un frontend React.
## Arquitectura
```
cpp/functions/viz/
├── map_tiles.h # NEW
├── map_tiles.cpp # NEW
└── map_tiles.md # NEW (impure)
cpp/apps/primitives_gallery/
├── demos_map.cpp # NEW
├── demos.h # MOD
├── main.cpp # MOD
└── CMakeLists.txt # MOD
```
### API propuesta
```cpp
namespace fn {
struct MapMarker { double lat, lon; ImU32 color; const char* label; };
struct MapPolyline { std::vector<ImVec2> latlon; ImU32 color; float thickness = 2.f; }; // ImVec2 = (lat, lon)
struct MapState {
double lat = 40.4168; // Madrid default
double lon = -3.7038;
int zoom = 6; // 0..18
std::string tile_url = "https://tile.openstreetmap.org/{z}/{x}/{y}.png";
std::string user_agent = "fn-registry-map/0.1 (egutierrez@aurgi.com)";
bool show_attribution = true; // OSM atribucion obligatoria
};
void map_tiles(const char* id, MapState&,
const MapMarker* markers, int n_markers,
const MapPolyline* polylines, int n_polylines,
ImVec2 size = {-1, -1});
}
```
## Tareas
### Fase 1 — Tile cache
- 1.1 Cache en disco bajo `~/.cache/fn-registry/tiles/{z}/{x}/{y}.png`. Crear directorio si no existe.
- 1.2 Cache en RAM (LRU) de `GlTexture` ya subidas, max 256 entradas.
- 1.3 Funcion `tile_get_or_fetch(z, x, y)`: si en RAM → return; si en disco → load (gl_texture_load); si no → spawn thread que descarga via cpp-httplib y guarda en disco. Mientras tanto, devolver placeholder.
### Fase 2 — Proyeccion + interaction
- 2.1 Funciones puras `lonlat_to_xy(lat, lon, zoom) -> (px, py)` y `xy_to_lonlat`.
- 2.2 `map_tiles_handle_input`: drag pan, wheel zoom (clamp 0-18), mantener punto bajo cursor durante zoom.
### Fase 3 — Render
- 3.1 Calcular tiles visibles dado el viewport y zoom.
- 3.2 Para cada tile visible: `ImGui::Image(texture, screen_rect)` o `AddImage` en el DrawList.
- 3.3 Markers: `AddCircleFilled` + texto.
- 3.4 Polylines: convertir lat/lon a screen y `AddPolyline`.
- 3.5 Atribucion en esquina inferior derecha: `"© OpenStreetMap contributors"`.
### Fase 4 — User-Agent + ToS
- 4.1 Enviar `User-Agent` custom (OSM lo exige). Documentar en `.md` que el usuario debe poner su contacto.
- 4.2 Cap a 2 requests/segundo simultaneos para no abusar del tile server.
### Fase 5 — Gallery demo
- 5.1 `demos_map.cpp` con `demo_map_tiles()`: mapa centrado en Espana, 5 markers en ciudades, polyline conectandolas.
- 5.2 Registrar.
### Fase 6 — Tests + docs
- 6.1 Tests puros de proyeccion (Madrid → conocidos pixels a zoom 10).
- 6.2 `./fn index` + `./fn show map_tiles_cpp_viz`.
## Ejemplo de uso
```cpp
fn::MapState m;
fn::MapMarker markers[] = {
{40.4168, -3.7038, IM_COL32(255,80,80,255), "Madrid"},
{41.3851, 2.1734, IM_COL32(80,150,255,255), "Barcelona"},
};
fn::map_tiles("##m", m, markers, 2, nullptr, 0);
```
## Decisiones de diseño
- **OSM tile server por defecto**: gratis, sin API key. Documentar limite y alternativa (carto, stamen) cambiando `tile_url`.
- **Cache en `~/.cache/fn-registry/tiles/`**: respeta XDG; persistente entre runs.
- **Sin GeoJSON parsing en MVP**: solo markers/polylines via API. GeoJSON en issue futuro.
## Riesgos
- **Bloqueo de IP por OSM**: documentar terminos de uso (max 1 req/s recomendado, User-Agent unico).
- **Tiles a alta latitud**: documentar que la proyeccion Web Mercator distorsiona >85°.
- **Threading de descargas**: cuidado con marshalling de texturas GL (deben crearse en hilo con contexto). Patron: descargar PNG bytes en thread, crear GL texture en main thread.
@@ -0,0 +1,160 @@
---
id: "0036"
title: "C++ image_canvas + webcam_texture"
status: pendiente
type: feature
domain:
- cpp-stack
scope: multi-app
priority: baja
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0036 — C++ image_canvas + webcam_texture
## APP Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0036 |
| **Estado** | pendiente |
| **Prioridad** | baja |
| **Tipo** | feature — C++ core/gfx (cpp/functions/core, cpp/functions/gfx) |
## Dependencias
`gl_texture_load_cpp_gfx` (issue 0026) — recomendado. `tokens_cpp_core`. Independiente de los demas.
**Desbloquea:** UIs para etiquetado/anotacion de imagenes (CV, OCR debugging) y entrada de webcam para `shaders_lab` reactivo a video.
---
## Objetivo
Dos primitivos:
1. **`image_canvas_cpp_core`** — viewer de imagen con pan/zoom + capa de anotaciones (rectangulos, puntos, polilineas, texto). Drag para crear nuevas anotaciones; click para seleccionar; tecla Del para borrar.
2. **`webcam_texture_cpp_gfx`** — captura de webcam (V4L2 en Linux, Media Foundation en Windows) → GL texture actualizada cada frame. API parejo a `audio_capture`.
## Contexto
Casos de uso identificados:
- Inspeccionar resultados de un detector (rectangulos overlay).
- Etiquetar manualmente datasets pequeños in-app.
- Pasar webcam a shaders en `shaders_lab` (filtros video en tiempo real).
Ningun primitivo del registry actual cubre estos casos.
## Arquitectura
```
cpp/functions/core/
├── image_canvas.h # NEW
├── image_canvas.cpp # NEW
└── image_canvas.md # NEW (impure component)
cpp/functions/gfx/
├── webcam_texture.h # NEW
├── webcam_texture.cpp # NEW
└── webcam_texture.md # NEW (impure)
cpp/apps/primitives_gallery/
├── demos_image_webcam.cpp # NEW
├── demos.h # MOD
├── main.cpp # MOD
└── CMakeLists.txt # MOD
```
### API propuesta
```cpp
namespace fn {
// --- image_canvas ---
struct Annotation {
enum Kind { Rect, Point, Polyline, Text } kind;
std::vector<ImVec2> points; // image-space coords (no screen)
ImU32 color = IM_COL32(255, 80, 80, 255);
std::string label;
};
struct ImageCanvasState {
GLuint texture = 0; // caller-owned
int img_w = 0, img_h = 0;
float zoom = 1.f;
ImVec2 pan = {0, 0};
std::vector<Annotation> annotations;
int selected = -1;
enum Tool { ToolNone, ToolRect, ToolPoint, ToolPolyline } tool = ToolNone;
};
void image_canvas(const char* id, ImageCanvasState&, ImVec2 size = {-1, -1});
// --- webcam_texture ---
struct WebcamTextureConfig { int width = 640; int height = 480; int fps = 30; int device = 0; };
struct WebcamTexture { GLuint id = 0; int w = 0; int h = 0; bool ok() const { return id != 0; } };
WebcamTexture webcam_texture_start(const WebcamTextureConfig&);
void webcam_texture_stop(WebcamTexture&);
bool webcam_texture_update(WebcamTexture&); // copia ultimo frame a GPU; true si hubo nuevo
const char* webcam_texture_last_error();
}
```
## Tareas
### Fase 1 — image_canvas
- 1.1 Render: `ImGui::Image(texture, ...)` con transform por pan/zoom. Drag con MMB para pan, wheel para zoom.
- 1.2 Tool == Rect: drag con LMB en image-space crea anotacion `Rect` con 2 puntos.
- 1.3 Tool == Point: click LMB anade un Point.
- 1.4 Tool == Polyline: click anade puntos; doble-click cierra.
- 1.5 Hit-test para selection; Del key elimina la seleccionada.
- 1.6 `.md`.
### Fase 2 — webcam_texture (Linux V4L2)
- 2.1 Abrir `/dev/video<device>`, MMAP buffers, VIDIOC_STREAMON.
- 2.2 En `update`, dequeue buffer, convert YUYV → RGBA (CPU), upload a GL texture, requeue.
- 2.3 `.md` (`kind: function`, `purity: impure`, `error_type`).
### Fase 3 — webcam_texture (Windows Media Foundation, opcional)
- 3.1 Si `_WIN32`, usar `IMFSourceReader`. Convert NV12 → RGBA.
- 3.2 Documentar que macOS no se soporta en MVP.
### Fase 4 — Gallery demo
- 4.1 `demos_image_webcam.cpp`:
- `demo_image_canvas()`: carga `assets/sample.png` (si existe — fallback a rendered placeholder), permite pintar rectangulos.
- `demo_webcam_texture()`: start cam, mostrar preview + sliders RGB sobre un shader fullscreen que samplea la cam.
- 4.2 Registrar.
### Fase 5 — Tests + docs
- 5.1 Tests puros de transform image-space ↔ screen-space.
- 5.2 Smoke test webcam (skip si no hay /dev/video0).
- 5.3 `./fn index`.
## Ejemplo de uso
```cpp
auto cam = fn::webcam_texture_start({});
if (!cam.ok()) std::fprintf(stderr, "%s\n", fn::webcam_texture_last_error());
fn::run_app("cam", [&]{
fn::webcam_texture_update(cam);
ImGui::Image((ImTextureID)(intptr_t)cam.id, {cam.w*1.f, cam.h*1.f});
});
```
## Decisiones de diseño
- **Anotaciones en image-space**: pan/zoom no afectan coordenadas guardadas. Permite serializar a JSON (issue futuro).
- **YUYV → RGBA en CPU**: simple, ~5ms/frame a 640×480. Si hace falta, hacer un compute shader (issue 0027).
- **macOS sin webcam por ahora**: AVFoundation requiere ObjC++; no compensa en MVP.
## Riesgos
- **Permisos webcam**: documentar troubleshooting (`/dev/video0` permisos en Linux).
- **Driver V4L2 raro**: algunos formatos de pixel no son YUYV. Documentar fallback a MJPEG (con stb_image decode) o error claro.
- **`ImTextureID` semantics** entre backends: documentar.
@@ -0,0 +1,215 @@
---
id: "57"
title: "Extraer paneles ImGui reutilizables de las apps C++ a cpp/functions/ — roadmap"
status: pendiente
type: epic
domain:
- cpp-stack
- registry-quality
scope: cross-stack
priority: media
depends: []
blocks: []
related: []
created: 2026-05-09
updated: 2026-05-17
tags: []
---
## Contexto
Las apps C++ del registry (`cpp/apps/*`, `projects/*/apps/*`) llevan acumulando paneles ImGui que comparten patron pero viven dentro de cada app. Hay duplicacion observable y mas que va a aparecer (navegator_dashboard quiere chat panel igual que graph_explorer; el dashboard de Tabs va a parecerse al panel "Browsers").
Hoy ya estan en el registro `cpp/functions/core/`: `dashboard_panel`, `fullscreen_window`, `log_window`, `modal_dialog`, `tree_view`, `selectable_text`. La capa visual (tokens, theming, iconos, settings/about/menubar) vive en `fn_framework`. Esto cubre primitivas. Falta una tier intermedia: **paneles compuestos** (mas grandes que un widget, mas pequeños que una app).
Este issue documenta el roadmap de que extraer, cuando, y por que.
## Principios
1. **Rule of three** — no extraer hasta que haya 2-3 consumidores reales. Pre-extraer crea abstracciones malas.
2. **Consumidor primero** — escribir el panel inline en la app. Si una segunda app lo necesita, copiar+adaptar. Si una tercera lo pide, extraer.
3. **Parametrizable, no monolitico** — los paneles extraidos reciben un struct de config (callbacks, paths, env vars) en vez de hardcodear nombres.
4. **No exponer estado global del modulo extraido** — usar contexto opaco que cada app crea/destruye.
## Catalogo: paneles candidatos detectados
Revisado todas las apps C++ vivas. Por cada panel: lineas, consumidores actuales, consumidores potenciales, decision.
### Tier 0 — ya extraidos ✓
| Panel | Ubicacion |
|---|---|
| dashboard cards grid | `cpp/functions/core/dashboard_panel.{h,cpp}` |
| fullscreen window wrapper | `cpp/functions/core/fullscreen_window.{h,cpp}` |
| log window con buffer | `cpp/functions/core/log_window.{h,cpp}` |
| modal dialog | `cpp/functions/core/modal_dialog.{h,cpp}` |
| tree view | `cpp/functions/core/tree_view.{h,cpp}` |
| selectable text | `cpp/functions/core/selectable_text.{h,cpp}` |
Estos definen el "lower-tier" — primitivas. Los nuevos extracts componen sobre estos.
### Tier 1 — extraer cuando aparezca el 3er consumidor
#### 1.1 `claude_chat_panel` (origen: graph_explorer/chat.cpp, 1241 LoC)
**Que es:** panel ImGui que arranca un subprocess `claude -p`, le envia mensajes del usuario, parsea stream-json, renderiza historial con tool-use detectado, expone callbacks para que la app recargue cuando el agente muta su BD.
**Consumidores hoy:** graph_explorer.
**Consumidores potenciales:**
- navegator_dashboard (issue navegator/0001) — chat para controlar Chrome.
- kanban (futuro) — agente para crear/mover cards.
- registry_dashboard — agente para crear functions/proposals.
**Por que tier 1 (no tier 0):**
- 59 referencias a `gx-cli`, `GX_OPS_DB`, `GX_APP_DB`, `agent_mutations`. Acoplamiento alto a graph_explorer.
- Refactor a config parametrizado es ~4-6h y modifica codigo que YA funciona — riesgo regresion.
- Con solo 2 consumidores no hay claridad de que parametrizar.
**Cuando extraer:** 3er consumidor pide chat. Hasta entonces: copiar+adaptar (graph_explorer mantiene el suyo, navegator copia el suyo).
**API propuesta cuando se extraiga:**
```cpp
struct ClaudeChatConfig {
const char* claude_bin; // "claude" o WSL path
const char* cli_tool_name; // "gx-cli", "cdp-cli", "kanban-cli", ...
const char* cli_tool_dir;
std::map<std::string, std::string> env_vars; // GX_*, NAVD_*, ...
const char* mcp_config_path; // o func que lo genera on-demand
const char* system_prompt; // opcional
std::function<int()> mutations_counter; // app-side reload trigger
const char* log_file_path;
};
namespace fn_ui {
struct ChatHandle; // opaco
ChatHandle* chat_create(const ClaudeChatConfig& cfg);
void chat_send(ChatHandle*, const char* user_text);
void chat_render(ChatHandle*, bool* p_open);
void chat_destroy(ChatHandle*);
}
```
#### 1.2 `jobs_queue_panel` (origen: graph_explorer/jobs.cpp + odr_console/jobs)
**Que es:** panel + sistema de jobs asincronos. Lista jobs en cola/running/done con barra de progreso, stdout/stderr capture, cancelable, persistente entre sesiones.
**Consumidores hoy:** graph_explorer (jobs.cpp + views_jobs.cpp), odr_console (panel Jobs).
**Consumidores potenciales:**
- navegator_dashboard — scraping jobs (un crawl es un job).
- registry_dashboard — fn index, fn run jobs.
**Por que tier 1:** ya hay 2 consumidores, parece duplicacion clara. **Inspeccionar diff entre ambos antes de extraer** — pueden estar implementados distinto y no reusarse del todo.
**Cuando extraer:** despues de auditar que graph_explorer/jobs.cpp y odr_console/jobs comparten >70% del codigo. Si si: 3er consumidor (navegator scraping) detona la extraccion.
**API propuesta:**
```cpp
namespace fn_ui {
struct Job {
std::string id;
std::string title;
std::function<int(JobContext&)> run; // retorna exit code
// ...
};
void jobs_panel_init(const char* persistence_db_path);
void jobs_panel_submit(const Job&);
void jobs_panel_render(bool* p_open);
}
```
### Tier 2 — extraer cuando aparezca el 2do consumidor (todavia no detectado)
#### 2.1 `project_switcher_panel` (origen: graph_explorer/project_manager.cpp)
**Que es:** sidebar/menu para cambiar entre subcarpetas de proyectos (cada proyecto tiene su DB y settings). Persiste el ultimo proyecto abierto.
**Hoy en:** graph_explorer.
**Potencial:** navegator_dashboard (perfiles browser como proyectos), registry_dashboard (apps como proyectos), kanban (boards).
**Cuando extraer:** cuando navegator_dashboard quiera multi-perfil persistente con settings por perfil. v3 del dashboard probablemente.
#### 2.2 `key_value_inspector_panel` (origen: graph_explorer/views.cpp `Inspector`)
**Que es:** panel "Inspector" generico que muestra propiedades de un objeto seleccionado en formato key:value, con edicion inline opcional. Hoy es ad-hoc para entities.
**Potencial:** navegator_dashboard (Tab Detail mostrar propiedades de pestaña), registry_dashboard (function detail), kanban (card details).
**Cuando extraer:** cuando dos paneles de "ver propiedades de cosa seleccionada" tengan codigo casi identico.
#### 2.3 `paste_and_extract_panel` (origen: graph_explorer/extract_panel.cpp)
**Que es:** panel que recibe texto pegado, invoca enricher Python, muestra entities extraidas + boton para insertarlas.
**Potencial:** navegator_dashboard (extraer texto seleccionado de pestaña), registry_dashboard (extraer descripcion para auto-fill function metadata).
**Cuando extraer:** 2do consumidor con flujo similar.
#### 2.4 `http_request_inspector_panel` (origen: registry_dashboard/http_client.cpp + futuro navegator_dashboard/network)
**Que es:** panel para ver una peticion HTTP (request line, headers, body) renderizado bonito. Read-only.
**Potencial:** navegator_dashboard (Network panel renderiza HAR entries), registry_dashboard (mostrar requests outgoing del fn_registry_api).
**Cuando extraer:** cuando 0070b (HAR record) sea consumido por algo que quiera renderizarlo bien.
### Tier 3 — NO extraer (especificos de su app)
| Panel | Por que NO |
|---|---|
| graph_explorer/types_registry | Acoplado a `types.yaml` schema especifico. |
| graph_explorer/node_groups | DuckDB + grafo + tipo "Table" del schema graph_explorer. |
| graph_explorer/layout_store | Posiciones de nodos del grafo, schema especifico. |
| graph_explorer/views.cpp::Legend | Mapa de tipos→colores especifico del grafo. |
| graph_explorer/views.cpp::Stats | Conteos por tipo de entity, schema especifico. |
| navegator_dashboard/browsers panel | Detector chrome.exe Win32, hereda WMI/CIM, especifico Windows. |
| odr_console/datasets panel | Listado especifico del flujo ODR. |
Estos viven en su app. Si emerge un patron despues, se reconsidera.
### Tier 4 — primitivas que sirven a TODOS los paneles (extraccion oportunista, sin app-driver)
Util tener antes de los Tier 1-2 para hacer la extraccion mas limpia:
| Primitiva | Para que |
|---|---|
| `subprocess_streamer.{h,cpp}` | Encapsula spawn + pipes + read-thread + queue. Lo usaria chat_panel + jobs_panel. |
| `mcp_config_writer.{h,cpp}` | Genera `.mcp.json` para Claude. Lo usaria chat_panel. |
| `app_db_init.{h,cpp}` | Patron generico de "abrir SQLite junto al exe + aplicar migrations embed.FS". Hoy cada app reimplementa. |
| `key_input_history.{h,cpp}` | Input + flecha arriba/abajo recupera entradas previas. Util en chat + REPL evaluate. |
| `json_log_renderer.{h,cpp}` | Render de stream JSON estilo claude-code (rol, tool_use, contenido). Util tanto en chat de graph_explorer como en futuros chats. |
Estas primitivas se extraen cuando empieza la extraccion de un Tier 1 que las necesita (no antes — caen en el "rule of three" tambien).
## Estrategia de adopcion
Por **demanda**:
1. **Hoy (tras este issue):** copiar+adaptar `chat.cpp` a `navegator_dashboard`. Documentar la duplicacion. NO extraer.
2. **Cuando aparezca 3er consumidor de chat** (o `jobs`, o `project_switcher`): pausar feature work, refactorizar el extract con vista 3D.
3. **Auditoria periodica trimestral**: cada 3 meses revisar este issue, contar consumidores actuales, decidir si activar extracts.
## Sub-issues iniciales (no abrir hasta tener 3er consumidor identificado)
- `0071a` — extract `claude_chat_panel` (cuando 3 apps lo pidan).
- `0071b` — extract `jobs_queue_panel` (auditar duplicacion graph_explorer/odr_console primero).
- `0071c` — extract `project_switcher_panel`.
- `0071d` — extract `key_value_inspector_panel`.
- `0071e` — extract `http_request_inspector_panel`.
## Definicion de hecho (issue 0071 cerrado)
- Catalogo arriba esta vivo, refleja apps actuales.
- Auditoria trimestral mantiene la lista actualizada.
- Sub-issues 0071a-e: cada uno abre cuando se cumple su criterio.
- Cuando todos los Tier 1 + 2 esten extraidos, este issue se marca completed.
## Anti-patron a evitar
- **Big-bang refactor**: extraer 5 paneles a la vez, con interfaces no validadas, rompiendo todas las apps. NO.
- **Pre-extraer "por si acaso"**: el panel mas dificil es el que solo tiene 1 consumidor — no sabes que parametrizar. NO.
- **Library wrapper sin control**: meter `claude_chat_panel` con 30 hooks/callbacks "para flexibilidad". KISS — solo expone lo que el 3er consumidor demuestra que necesita.
@@ -0,0 +1,169 @@
---
id: "0072a"
title: "gamedev — smoke SDL3 + sokol_gfx + ImGui (PC + WASM)"
status: pendiente
type: feature
domain:
- cpp-stack
- gamedev
scope: multi-app
priority: alta
depends: []
blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags: [ausente-ready, gamedev, cpp, wasm]
---
## Objetivo
Validar de forma temprana que el stack SDL3 + sokol_gfx + Dear ImGui compila y corre en PC (Windows + Linux) y WASM (Emscripten) con un binario "Hello sprite + shader" antes de invertir tiempo en runtime real.
## Salida esperada
App `cpp/apps/engine_smoke/` que:
1. Abre ventana SDL3 (1280x720, redimensionable).
2. Inicializa sokol_gfx (GL en desktop, WebGL2 en WASM).
3. Pinta:
- Un quad con textura cargada via `stbi_load` (ya en stack).
- Un fullscreen shader (gradiente animado).
- Un panel ImGui con FPS y boton "exit".
4. Compila a:
- `engine_smoke.exe` (Windows, MSVC o MinGW)
- `engine_smoke` (Linux, gcc/clang)
- `engine_smoke.html` + `engine_smoke.wasm` + `engine_smoke.js` (Emscripten)
5. WASM gzip ≤ **1.5 MB** (objetivo agresivo de Fase 0). Si no se cumple, documentar de donde viene el peso y plan de reduccion.
## Tareas
### 1. Vendoring de dependencias
`cpp/vendor/` ya existe. Añadir:
- `sokol/``sokol_gfx.h`, `sokol_app.h` NO (usamos SDL3), `sokol_log.h`, `sokol_glue.h` adaptado para SDL3 (init manual del contexto).
- `sdl3/` — clonar build estatico de SDL 3.x.
- ImGui ya esta en `cpp/vendor/imgui/`. Añadir backends `imgui_impl_sdl3.cpp` + `imgui_impl_sokol.cpp`. Si no existe oficial sokol backend, escribir uno minimo (~200 lineas).
### 2. CMake
```cmake
# cpp/apps/engine_smoke/CMakeLists.txt
add_executable(engine_smoke main.cpp)
target_link_libraries(engine_smoke PRIVATE SDL3::SDL3 imgui)
target_compile_definitions(engine_smoke PRIVATE SOKOL_GLCORE)
if(EMSCRIPTEN)
target_link_options(engine_smoke PRIVATE
-sUSE_WEBGL2=1
-sFULL_ES3=1
-sALLOW_MEMORY_GROWTH=1
-sINITIAL_MEMORY=33554432 # 32MB
--shell-file=${CMAKE_CURRENT_SOURCE_DIR}/shell.html
)
endif()
```
NO usar `add_imgui_app` (esa macro asume GLFW + framework desktop). Esta app es deliberadamente standalone para no contaminar `fn_framework` hasta que validemos el stack.
### 3. main.cpp minimo
```cpp
#include <SDL3/SDL.h>
#define SOKOL_IMPL
#include "sokol_gfx.h"
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_sokol.h"
int main(int, char**) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
SDL_Window* win = SDL_CreateWindow("engine_smoke", 1280, 720,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
SDL_GLContext ctx = SDL_GL_CreateContext(win);
sg_setup({ .environment = sokol_glue_env() });
ImGui::CreateContext();
ImGui_ImplSDL3_InitForOpenGL(win, ctx);
ImGui_ImplSokol_Init();
bool running = true;
while (running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
ImGui_ImplSDL3_ProcessEvent(&e);
if (e.type == SDL_EVENT_QUIT) running = false;
}
// render: clear, sprite quad, fullscreen shader, ImGui overlay
sg_begin_pass({ .swapchain = sokol_glue_swapchain() });
// ... pipeline + draw calls
ImGui_ImplSokol_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGui::Begin("Smoke"); ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
if (ImGui::Button("exit")) running = false;
ImGui::End();
ImGui::Render();
ImGui_ImplSokol_RenderDrawData(ImGui::GetDrawData());
sg_end_pass();
sg_commit();
SDL_GL_SwapWindow(win);
}
sg_shutdown();
return 0;
}
```
### 4. Shell HTML para WASM
`shell.html` minimo: canvas + loader. Sin Bootstrap, sin nada extra. Ver Emscripten template default y stripear.
### 5. Pipeline de build
Funcion bash nueva: `bash/functions/pipelines/build_wasm_cpp_app.sh` que:
1. Verifica `emcc` instalado (descarga `emsdk` si falta).
2. `emcmake cmake -B build/wasm -S cpp/apps/engine_smoke`
3. `cmake --build build/wasm --target engine_smoke`
4. Reporta tamaños raw + gzip de `.wasm`, `.js`, `.html`.
5. Falla si supera budget.
### 6. e2e_checks en `app.md`
```yaml
e2e_checks:
- id: build_pc
cmd: "cmake --build build --target engine_smoke -j"
timeout_s: 300
- id: build_wasm
cmd: "bash bash/functions/pipelines/build_wasm_cpp_app.sh engine_smoke"
timeout_s: 600
- id: size_budget_wasm
cmd: "test $(stat -c%s build/wasm/engine_smoke.wasm.gz) -lt 1572864" # 1.5 MB
```
## Decisiones a documentar
- ¿`sokol_gfx` o usar GL directo? — sokol_gfx, abstrae backend mobile despues sin tocar codigo de juego.
- ¿`sokol_app` o `SDL3` para windowing? — SDL3, mejor soporte mobile + audio + gamepad.
- ¿Imgui-sokol backend oficial? — verificar `floooh/sokol-samples` repo. Si no, escribirlo (renderable en una sesion, ~200 LoC).
## Criterio de exito
- [x] Compila en Linux + Windows (MSVC).
- [x] Compila en WASM con emscripten ≥3.1.50.
- [x] WASM gzip ≤ 1.5 MB.
- [x] Sprite + shader + ImGui visibles en navegador (Chrome + Firefox).
- [x] FPS estable ≥60 en navegador moderno.
## Riesgos
- Backend ImGui+sokol — si no hay oficial, escribir uno (no bloqueante).
- SDL3 still relativamente nuevo (release oct 2024). Si bugs gordos, fallback a SDL2.
- Emscripten + sokol_gfx WebGL2 — verificado en `floooh/sokol-samples`, debe funcionar.
## No-objetivos (Fase 0)
- NO audio funcional aun.
- NO touch input.
- NO build mobile (Android/iOS).
- NO integracion con `fn_framework`.
- NO assets reales.
@@ -0,0 +1,102 @@
---
id: "0082"
title: "Compilar binario `sd` (stable-diffusion.cpp) para sdcli_generate_go_ml"
status: pendiente
type: feature
domain:
- cpp-stack
- imagegen
scope: multi-app
priority: media
depends: []
blocks: []
related: []
created: 2026-05-13
updated: 2026-05-17
tags: [ausente-ready]
---
## Objetivo
Compilar el binario `sd` de [leejet/stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp)
con backend CUDA en este host (WSL2 + RTX 3070) e instalarlo en `$PATH`. Habilita
los tests reales de `sdcli_generate_go_ml` y el wrapper Go subprocess (Ola 3.C ya
construido pero con tests en `skip` por falta de binario).
## Contexto
- Funcion Go `sdcli_resolve_binary_go_ml` busca `sd` o `sd-cli` en `$PATH`.
- `sdcli_generate_go_ml` orquesta args via `genconfig_to_sdcli_args_go_ml`, lanza
subproceso con `subprocess_stream_go_core`, parsea progreso con
`sdcli_parse_progress_go_ml`, lee PNG de salida.
- Tests `TestSdcliResolveBinary_NotFound`, `..._Hint` pasan; `TestSdcliGenerate_RequiresBinary`
hace `t.Skip()` porque `sd` no existe en `$PATH`.
- Backend `sdcpp_python_load_py_ml` ya validado con SD Turbo (CPU, 27s/imagen).
El binario Go nativo deberia ser comparable o mejor con CUDA.
## Arquitectura
Archivos NUEVOS sugeridos:
- `bash/functions/infra/build_sd_cpp.sh` + `.md` — funcion del registry que clona y
compila stable-diffusion.cpp con flags configurables (`-DSD_CUDA=ON`, `-DSD_FLASH_ATTN=ON`,
`-DSD_FAST_SOFTMAX=ON`). Idempotente.
- `bash/functions/infra/install_sd_cpp_bin.sh` + `.md` — copia el binario compilado
a `~/.local/bin/sd` o equivalente en `$PATH`.
NO modificar:
- `functions/ml/sdcli_*.go` — su contrato no cambia, solo se desbloquea el path feliz.
## Tareas
1. Compilacion
1.1. Clonar `https://github.com/leejet/stable-diffusion.cpp` en `sources/stable-diffusion.cpp/`.
1.2. Verificar requisitos: `cmake >= 3.18`, `gcc`, CUDA toolkit (instalable con
`cuda_toolkit_check_bash_infra`). Si CUDA toolkit falta, instalarlo o
documentar pasos manuales.
1.3. Crear `bash/functions/infra/build_sd_cpp.sh` que:
- Acepta flag `--backend cuda|cpu|vulkan`
- cmake -B build -DSD_CUDA=ON (segun flag)
- cmake --build build -j
- Verifica que `build/bin/sd` o `build/sd` existe.
1.4. Crear `bash/functions/infra/install_sd_cpp_bin.sh` que copia `sd` a
`~/.local/bin/` y verifica `command -v sd`.
2. Smoke test
2.1. Ejecutar `sd --version` desde Go: `SdcliResolveBinary("")` debe encontrarlo.
2.2. Generar 1 imagen con SD Turbo `.safetensors` y comparar tiempo vs
`sdcpp_python` (esperado: similar o mejor con CUDA).
3. Indexar
3.1. `./fn index` y verificar 2 funciones nuevas.
4. Cleanup
4.1. Re-run `CGO_ENABLED=1 go test -tags fts5 -run TestSdcliGenerate ./functions/ml/`
`TestSdcliGenerate_RequiresBinary` debe pasar sin skip.
## Ejemplo de uso
```bash
fn run build_sd_cpp --backend cuda
fn run install_sd_cpp_bin
sd --help # ya en PATH
./fn doctor ml # sd_cli debe pasar a "ok"
```
## Decisiones
- **Compilar en `sources/`** (gitignored) — no commitear binario.
- **Instalar en `~/.local/bin/`** — sin sudo, en `$PATH` por defecto en shells.
- **Backend CUDA preferido** — esta maquina tiene RTX 3070 (8GB). CPU es fallback.
## Prerequisitos
- Issues 3.B/3.C completados (sdcpp_python + sdcli go scaffolding).
- Modelo SD Turbo en vault (ya esta).
## Riesgos
- CUDA toolkit no instalado: `nvcc` ausente segun `fn doctor ml`. Mitigacion:
fallback CPU (`-DSD_CUDA=OFF`) o instalar toolkit primero.
- API rota entre versiones de `sd`: pinear release concreto (tag git) en el script.
- Binario grande (~200MB con CUDA libs estaticas): vale, sources/ esta gitignored.
@@ -0,0 +1,172 @@
---
id: "0095"
title: "Frontend C++ ImGui para `dag_engine`"
status: pendiente
type: feature
domain:
- cpp-stack
- frontend
scope: multi-app
priority: alta
depends: []
blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
---
# 0095 — Frontend C++ ImGui para `dag_engine`
**Status:** pendiente
**Created:** 2026-05-15
**Type:** app
**Blocks:** 0096 (data_factory) — necesita frontend C++ unificado para scheduler
**Related:** `apps/dag_engine` (Go + Vite/React actual), `projects/fn_monitoring/apps/registry_dashboard` (referencia pubsub)
## Problema
`dag_engine` (alternativa propia a Dagu, ya existente) hoy sirve dos modos:
- CLI (`run/list/status/validate/server`).
- Web (Vite + React + Mantine en `apps/dag_engine/frontend/`).
El resto del ecosistema de monitorizacion del registry esta en C++ ImGui (`registry_dashboard`, `call_monitor`, futuro `data_factory`). Forzar al usuario a saltar al navegador para gestionar/observar DAGs rompe el flujo. Ademas la app `data_factory` (issue 0096) necesita embeber vista de scheduler para mostrar que DAG dispara cada extractor — si esa vista es web, no encaja en su UI ImGui.
## Objetivo
Anadir app C++ ImGui `dag_engine_ui` que cubre el caso "ver/lanzar/inspeccionar DAGs" con el **mismo backend** (`dag_engine server` HTTP+WS) y reusa el patron pubsub de `registry_dashboard` (HTTP REST + WebSocket live updates).
NO sustituye el frontend web — convive. El web sigue util para acceso remoto al VPS; el C++ es para uso local en escritorio.
## Piezas
### Backend (apps/dag_engine, cambios minimos)
1. **WS hub `DagRunHub`** en `apps/dag_engine/events.go` siguiendo el patron de `CallMonitorHub` (sqlite_api/events.go):
- Hub global con N subscribers WS.
- Ticker arranca solo con >=1 subscriber.
- Polling watermark a tabla `dag_runs` + `dag_step_results` cada 500ms.
- Snapshot inicial al conectar (lista DAGs + ultimos runs).
- Broadcast de eventos: `run_started`, `step_completed`, `run_finished`, `schedule_updated`.
2. **Endpoint** `GET /api/ws/dagruns` (upgrade WebSocket).
3. **Endpoint** `POST /api/dags/:name/run` (Run Now) — ya existe; verificar que devuelve `run_id` inmediato y el hub broadcastea el progreso.
4. **CORS** ya abierto en `middleware.go`.
### App C++ ImGui (`apps/dag_engine_ui/`)
Scaffolding via `fn run init_cpp_app dag_engine_ui --desc "Frontend ImGui para dag_engine"`.
1. **Capa HTTP**: `data_http.{cpp,h}` clona patron de `registry_dashboard/data_http.{cpp,h}` (cpp-httplib + nlohmann/json). Endpoints:
- `GET /api/dags` -> lista DAGs.
- `GET /api/dags/:name` -> detalle.
- `GET /api/dags/:name/runs` -> historial.
- `GET /api/runs/:id` -> timeline pasos.
- `POST /api/dags/:name/run` -> dispara.
- `POST /api/dags/:name/validate` -> valida YAML.
2. **Capa WS**: `ws_client.{cpp,h}` reusado tal cual de `registry_dashboard`. Conecta a `ws://127.0.0.1:8090/api/ws/dagruns`.
3. **Tabs** (panels via `cfg.panels`):
- **DAG List** — tabla: name, schedule (cron), last_status, last_run_at, next_run_at, tags. Reusa `table_view_cpp_viz`. Click fila -> DAG Detail.
- **DAG Detail** — header + metadata + boton "Run Now" + historial ultimos N runs (`table_view_cpp_viz`). Reusa `page_header_cpp_core`, `button_cpp_core`, `badge_cpp_core`.
- **Run Detail** — timeline de steps con stdout/stderr expandible. Reusa `tree_view_cpp_core` y un componente nuevo `timeline_cpp_viz` si no existe (delegar a fn-constructor).
- **Schedule** — vista cron: para cada DAG con `schedule:` lo siguiente que va a disparar (`next_cron_time_go_core`). Mini-calendario opcional.
- **Health** — kpis: runs_24h, success_rate, p95_duration, failed_runs. Reusa `kpi_card_cpp_viz`.
4. **Live updates**: WS recibe eventos -> push a ringbuffer en memoria -> tabs leen del ringbuffer en cada `render`. Run en curso se anima (spinner en columna status).
5. **Config**: `--api-url http://127.0.0.1:8090` (default localhost). Persistencia en `app_settings.ini` (gestionado por `app_settings_cpp_core`).
### Frontmatter `app.md`
```yaml
---
name: dag_engine_ui
lang: cpp
domain: tui
description: "Frontend ImGui para dag_engine. Lista, lanza e inspecciona DAGs con live updates via WS. Equivalente local al frontend web."
tags: [imgui, dashboard, dag, scheduler, http, websocket]
uses_functions:
- kpi_card_cpp_viz
- bar_chart_cpp_viz
- table_view_cpp_viz
- dashboard_panel_cpp_core
- dashboard_grid_cpp_core
- page_header_cpp_core
- badge_cpp_core
- button_cpp_core
- icon_button_cpp_core
- toolbar_cpp_core
- text_input_cpp_core
- select_cpp_core
- tree_view_cpp_core
- empty_state_cpp_core
- modal_dialog_cpp_core
- toast_cpp_core
uses_types: []
framework: "imgui"
entry_point: "main.cpp"
dir_path: "apps/dag_engine_ui"
repo_url: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/dataforge/dag_engine_ui"
---
```
Tag de grupo: anadir `scheduler` (grupo nuevo si >=3 funciones lo respaldan; revisar antes — si no, dejar plano).
### Funciones nuevas a delegar (estimacion)
- `timeline_cpp_viz` — componente ImGui que pinta pasos de un run con duracion + status + expand stdout/stderr. Reusable por `data_factory` v2.
- `cron_explain_go_core` (puro) — dado `"0 */15 * * *"` devuelve string humano "every 15 min". Usado en DAG List.
- `http_poll_json_cpp_core` (impuro) — wrapper sobre cpp-httplib que hace GET periodico con backoff y publica deltas via callback. Reusable.
Si patrones se repiten en `registry_dashboard` (cabecera HTTP+WS hibrido) -> proposal de extraer un mini-cliente comun `cpp/functions/core/http_ws_client.{cpp,h}`. NO crear inline.
## Aceptacion
- `fn run init_cpp_app dag_engine_ui` ejecutado, scaffolding limpio.
- `dag_engine server` expone `/api/ws/dagruns` (hub con register/unregister/snapshot/delta).
- App C++ compila en Linux y Windows (`build_cpp_windows_bash_infra dag_engine_ui`).
- Smoke: con `dag_engine server` corriendo y un DAG ejemplo, abrir app -> ve DAG en lista, click "Run Now" -> timeline aparece en vivo sin refresh.
- `e2e_checks` en `app.md`:
- `build_cmake``cmake --build cpp/build -j --target dag_engine_ui`.
- `self_test``./dag_engine_ui --self-test` arranca, valida conexion HTTP a `dag_engine`, sale 0/1.
- `pytest` opcional — script que arranca `dag_engine server` con DAG dummy, lanza UI headless (xvfb), spera evento WS, sale 0/1.
- `fn doctor cpp-apps` no reporta drift sobre `dag_engine_ui`.
- `uses_functions` declarado en `app.md` y los `.cpp` del registry listados en `CMakeLists.txt` coinciden (`fn doctor uses-functions`).
## No-objetivos
- Editar YAML de DAGs desde la UI (v2 — por ahora solo lectura + Run Now + Validate).
- Generar nuevos DAGs visualmente tipo drag&drop (v2).
- Sustituir el frontend web — convive.
- Soporte multi-engine (varios `dag_engine` remotos). Asume 1 backend local.
## Riesgos
| Riesgo | Mitigacion |
|---|---|
| WS hub anade peso al binario `dag_engine` | Mismo patron que `sqlite_api` (~150 LOC); negligible. |
| `cpp-httplib` no soporta WS upgrade limpio | `registry_dashboard/ws_client.cpp` ya implementa RFC 6455 manual sobre TCP. Reusar tal cual. |
| Doble UI (web + C++) -> deriva | Un solo backend, mismos endpoints. Si web cambia, C++ se entera por contrato HTTP. |
| Cron parser duplica logica si se anade `cron_explain` | Funcion pura nueva, atomica, justificada (UX). |
## Dependencias
- `apps/dag_engine` corriendo y aceptando conexiones.
- `cpp/functions/viz/*` y `cpp/functions/core/*` ya existentes (ver `uses_functions`).
- `cpp-httplib` y `nlohmann/json` vendored (igual que `registry_dashboard`).
## Plan de ejecucion (sub-tareas)
1. **Backend**: anadir `events.go` con `DagRunHub` + handler WS en `dag_engine`. Commit.
2. **Scaffolding**: `fn run init_cpp_app dag_engine_ui`. Commit.
3. **Capa HTTP**: copiar y adaptar `data_http.{cpp,h}` desde `registry_dashboard`. Endpoints DAG. Commit.
4. **Capa WS**: copiar `ws_client.{cpp,h}` tal cual. Conectar a `/api/ws/dagruns`. Commit.
5. **Tabs DAG List + Detail + Run Detail**. Commit por tab.
6. **Schedule + Health tabs**. Commit.
7. **Funciones nuevas** (`timeline_cpp_viz`, `cron_explain_go_core`, `http_poll_json_cpp_core`) — delegar a fn-constructor en paralelo en mismo turno. Commit por funcion.
8. **e2e_checks + redeploy_cpp_app_windows**. Commit final.
Cada paso: rama TBD propia (`issue/0095-<slug>`), merge `--no-ff` a master.
## Telemetria objetivo
Tras este issue:
- `dag_engine_ui` aparece en `function_stats` con `calls > 0` (lanzamientos via `is_cpp_app_running_windows_bash_infra`).
- `cron_explain_go_core` con `consumer_apps_count >= 1`.
- `timeline_cpp_viz` con consumidor declarado en `uses_functions` de `dag_engine_ui` + candidato a reuso en `data_factory` (issue 0096).
@@ -0,0 +1,134 @@
---
id: "0110"
title: "Gap registry: helper HTTP cliente C++ (curl/popen) reutilizable"
status: pendiente
type: feature
domain:
- cpp-stack
- registry-quality
scope: registry
priority: media
depends: []
blocks:
- "0111"
related:
- "0106"
created: 2026-05-18
updated: 2026-05-18
tags: [http, cpp, registry-gap, curl, helper, ausente-ready]
---
# 0110 — Helper HTTP cliente C++ en el registry
## Problema
Hoy no existe funcion HTTP cliente reutilizable en `cpp/functions/`. Cada app C++ que necesita
golpear un endpoint reinventa la capa:
| App | Fichero | Tecnica | LOC aprox |
|---|---|---|---|
| `apps/services_monitor/` | `http_client.cpp` | cURL popen/WinHTTP segun plataforma | ~150 |
| `apps/dag_engine_ui/` | inline en `main.cpp` | curl CLI via popen + parse | ~80 |
| `apps/data_factory/` | inline | popen curl | ~60 |
| `cpp/functions/core/llm_anthropic.cpp` | propio | cURL popen — solo Anthropic | — |
| `apps/process_explorer/` (issue 0111) | `http_client.cpp` local | pendiente — clonara services_monitor | ~150 esperados |
El patron ya supera el umbral `>2x` que dispara la regla de promocion (CLAUDE.md
"Si patron se repite >2x → propose nueva funcion via fn-constructor"). Cada app duplica:
- Detection de plataforma (Linux: `popen("curl -s ...")`, Win: `WinHTTPOpen`/popen)
- Manejo de basicAuth / Bearer tokens
- Timeouts
- Captura de body + status code
- Manejo de errores transitorios (DNS, conexion rechazada)
## Decision
Anadir al registry dos funciones C++ en dominio `core` (o `infra`):
### `http_request_cpp_core` (impure)
```cpp
namespace fn_http {
struct Request {
std::string method; // "GET", "POST", "PUT", "DELETE"
std::string url;
std::vector<std::pair<std::string,std::string>> headers;
std::string body; // raw bytes (JSON, etc.)
int timeout_ms = 5000;
std::string bearer_token; // shortcut: anade Authorization: Bearer <token>
std::string basic_user; // shortcut: anade Authorization: Basic base64(user:pass)
std::string basic_pass;
};
struct Response {
int status = 0; // 0 = error de transporte
std::string body;
std::vector<std::pair<std::string,std::string>> headers;
std::string error; // vacio si OK
int64_t duration_ms = 0;
};
Response request(const Request& req);
}
```
Implementacion: cURL via popen (portable WSL+Win+Linux, igual que `llm_anthropic`).
Si en el futuro queremos rendimiento real, swap a libcurl linkado estaticamente
o WinHTTP via `#ifdef _WIN32` — interfaz Request/Response no cambia.
### `http_get_json_cpp_core` (impure, pure wrapper)
Helper que envuelve `http_request` + parse JSON (via `nlohmann::json` o similar
ya disponible en el repo) para los casos comunes:
```cpp
namespace fn_http {
// Devuelve parsed JSON o lanza si status != 2xx
nlohmann::json get_json(const std::string& url,
const std::string& bearer_token = "",
int timeout_ms = 5000);
}
```
## Plan de migracion
Tras crear las funciones, abrir issue separado por cada consumer para migrar:
1. `apps/services_monitor/http_client.cpp` -> usar `fn_http::request`
2. `apps/dag_engine_ui/main.cpp` (inline)
3. `apps/data_factory/` (inline)
4. `cpp/functions/core/llm_anthropic.cpp` — refactor para usar `fn_http::request` por
debajo (mantiene API publica)
5. `apps/process_explorer/` (issue 0111) — nace ya usando el helper
## Criterios de aceptacion
- [ ] `cpp/functions/core/http_request.{cpp,h,md}` registrado en `registry.db`
- [ ] `cpp/functions/core/http_get_json.{cpp,h,md}` idem
- [ ] Tests visuales o de integracion contra `httpbin.org` (200/404/timeout/auth)
- [ ] Frontmatter completo (`params`/`output`/`tags`/`example`)
- [ ] `.md` cumple contrato self-doc (`## Ejemplo`, `## Cuando usarla`, `## Gotchas`)
- [ ] Al menos 1 consumer migrado para validar API (recomendado `services_monitor`)
- [ ] `fn doctor uses-functions` limpio
## Gotchas conocidos
- cURL popen en Windows necesita `curl.exe` en PATH — todos los WSL/Win lo tienen,
pero documentar en `## Gotchas`.
- Bodies binarios: popen complica el escape; primera version solo string bodies.
- TLS verify: por defecto on; permitir `req.insecure = true` solo para testing.
- Timeouts: cURL `--max-time` cubre handshake+transfer; documentar diferencia con
read-timeout puro.
## Por que no usar libcurl linkado
- `popen("curl ...")` no requiere anadir libcurl al toolchain MinGW cross-compile
(que ya costo configurar). `llm_anthropic` lleva meses funcionando asi.
- Cuando aparezca un caso real de latencia (>10 req/s sostenido), abrimos issue
separado para swap a libcurl.
## Out of scope (no en este issue)
- WebSocket / SSE — cliente WS C++ es otro gap; abrir issue propio cuando aplique.
- Cliente gRPC.
- Streaming responses (SSE chunk-by-chunk) — usar caso de `dag_engine_ui` para
decidir cuando.
+121
View File
@@ -0,0 +1,121 @@
---
id: "0130"
title: Kanban C++ v2 — gestor de dev/issues y dev/flows con backend Go + frontend ImGui
status: pendiente
type: epic
domain:
- cpp-stack
- apps-infra
- dev-ux
scope: multi-app
priority: alta
depends: []
blocks: []
related:
- "0112"
- "0119"
tags:
- kanban
- cpp
- imgui
- dev_ux
- issues
- flows
created: "2026-05-22"
updated: "2026-05-22"
---
# 0130 — Kanban C++ v2
**Status:** pendiente
## Por que
La v1 (`apps/kanban_cpp` borrada el 2026-05-22) mezclaba paneles ajenos al dominio kanban (agent runs, DoD, worktrees, calendar) y un backend que no era reutilizable. Para gestionar los 98 issues activos + 12 flows del proyecto necesitamos una vista board nativa, sin web, con edicion bidireccional de los archivos markdown.
## Que entrega
App kanban_cpp v2 con dos piezas:
1. **Backend Go** (`apps/kanban_cpp/backend/`) — service HTTP en puerto 8487.
- Parser bidireccional MD <-> SQLite (cache).
- Watcher fsnotify sobre `dev/issues/` (+ `completed/`) y `dev/flows/`.
- Endpoints REST: `/api/issues`, `/api/issues/{id}` (GET/PATCH), `/api/flows`, `/api/flows/{id}`, `/api/meta`, `/api/sse`.
- PATCH a issue reescribe el frontmatter en disco preservando body + orden de campos.
2. **Frontend C++ ImGui** (`apps/kanban_cpp/`) sobre el framework `fn::run_app`.
- Panel **Board**: columnas por status (pendiente / in-progress / bloqueado / completado). Drag-drop = PATCH status.
- Panel **Flows**: lista de flows con detalle.
- Panel **Filtros** (Aside): multi-select domain, scope, priority, tags.
- Panel **Detalle**: edicion de campos frontmatter de un issue (status, priority, scope, tags, depends, blocks).
- SSE para refrescar tras cambios externos en disco.
## Sub-issues
- **0130a** — parser MD + scan dirs (funciones registry).
- **0130b** — backend Go: schema + handlers + watcher + SSE.
- **0130c** — frontend C++: paneles + http client.
Cada sub-issue mergeable independiente en su rama corta TBD.
## Reusa del registry
Backend Go:
- `sqlite_open_go_infra`, `sqlite_apply_migrations_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`
- `random_hex_id_go_core`
Frontend C++:
- `http_request_cpp_core`
- `sse_client_cpp_core`
- `data_table_cpp_viz` (lista flows)
- `kpi_card_cpp_viz` (contadores por status)
## Crea (delegadas a fn-constructor en 0130a)
- `parse_issue_md_go_infra` — lee .md → struct (frontmatter YAML + body).
- `write_issue_md_go_infra` — escribe struct → .md preservando body + orden de campos.
- `scan_issues_dir_go_infra` — walk `dev/issues/` + `dev/issues/completed/`.
- `scan_flows_dir_go_infra` — walk `dev/flows/`.
- `watch_dir_fsnotify_go_infra` (si no existe) — events channel.
## DoD
- `fn doctor` verde para ambas apps (artefacts + e2e).
- `e2e_checks` en ambos `app.md` (build + health + self-test).
- Drag-drop en frontend reescribe el `.md` correspondiente y `git diff` lo muestra (solo frontmatter, body intacto).
- Trio obligatorio (`description` + `icon.phosphor` + `icon.accent`) en ambos `app.md`.
- Sub-repos Gitea creados (`dataforge/kanban_cpp` reactivado o nuevo, mismo nombre).
dod_evidence_schema:
- id: backend_health
kind: cmd
expected: "curl -fsS http://localhost:8487/api/health == 200"
required: true
- id: api_issues_count
kind: cmd
expected: "curl -fsS http://localhost:8487/api/issues | jq 'length' >= 90"
required: true
- id: patch_writes_md
kind: cmd
expected: "PATCH /api/issues/0130 status=in-progress reescribe dev/issues/0130-*.md (git diff muestra solo status)"
required: true
- id: frontend_self_test
kind: cmd
expected: "./cpp/build/linux/apps/kanban_cpp/kanban_cpp --self-test exit 0"
required: true
- id: board_screenshot
kind: screenshot
expected: "kanban_cpp Board panel con 4 columnas pobladas con issues reales"
required: true
## Anti-scope
NO incluye en esta version:
- Grafo de dependencias (depends/blocks/related visual).
- Edicion de body MD desde la app (solo frontmatter).
- Multi-PC sync (backend es local).
- Crear issues nuevos desde la UI (solo editar existentes).
- DoD evidence panel, agent runs, calendar, worktrees (la v1 los mezclaba — fuera).
@@ -0,0 +1,85 @@
---
id: "0130c"
title: "Frontend C++ ImGui kanban_cpp v2: board + flows + filtros + detalle"
status: pendiente
type: app
domain:
- cpp-stack
- dev-ux
scope: app-scoped
priority: alta
depends: ["0130b"]
blocks: []
related:
- "0130"
created: 2026-05-22
updated: 2026-05-22
tags: [cpp, imgui, kanban, frontend]
flow: "0130"
---
# 0130c — Frontend C++ ImGui kanban_cpp v2
**Status:** pendiente
## Por que
UI nativa sobre el backend 0130b. Aprovecha el framework `fn::run_app` (menubar, layouts, settings, about, log) y los componentes del registry (`data_table`, `kpi_card`, `http_request`, `sse_client`).
## Estructura
```
apps/kanban_cpp/
app.md
appicon.ico
CMakeLists.txt
main.cpp # fn::run_app + cfg.panels
data.h / data.cpp # http client + state global (issues, flows, filters)
panel_board.cpp # 4 columnas + drag-drop
panel_flows.cpp # tabla via data_table_cpp_viz
panel_filters.cpp # Aside con multi-select
panel_detail.cpp # form editable del issue seleccionado
panels.h
```
## Trio obligatorio (`app.md`)
```yaml
description: "Kanban C++ v2 para gestionar dev/issues y dev/flows del registry"
icon:
phosphor: "kanban"
accent: "#a855f7"
```
## Paneles
1. **Board** (`TI_KANBAN " Board"`) — 4 columnas (pendiente / in-progress / bloqueado / completado). Cada card: id + title (trunc 60) + priority badge + first domain chip. Drag-drop con `ImGui::BeginDragDropSource/Target` -> PATCH status.
2. **Flows** (`TI_FLOW " Flows"`) — `data_table_cpp_viz` con columnas id/title/status/kind. Click fila → carga detail.
3. **Filters** (`TI_FUNNEL " Filters"`) — AppShell.Aside-equivalente (panel lateral fijo). Multi-select por domain, scope, priority, tags. Estado local; rebuild request query.
4. **Detail** (`TI_INFO " Detail"`) — modal/panel lateral con form: status (combo), priority (combo), scope (combo), tags (chips editables), depends/blocks (listas), body (read-only multiline).
## HTTP client (data.cpp)
- `fetch_issues(filters)` → GET con query string → parse JSON → vector<Issue>.
- `fetch_flows()` → similar.
- `patch_issue(id, partial)` → PATCH JSON → recibe issue actualizado.
- `subscribe_sse()` thread aparte → push events a queue mutex → consumir en main loop → re-fetch afectados.
Usa `http_request_cpp_core` + `sse_client_cpp_core`. JSON via `nlohmann/json` (ya en cpp/vendor o sacar al header-only).
## DoD
- `cmake --build cpp/build/linux --target kanban_cpp -j` verde.
- `./cpp/build/linux/apps/kanban_cpp/kanban_cpp --self-test` exit 0:
- inicializa contexto ImGui sin display.
- parsea respuesta JSON sintetica.
- no toca red salvo si `--backend http://...` se pasa.
- e2e_checks en `app.md`: build + self_test + backend_health (corre backend en background) + smoke (drag-drop reescribe MD).
- Captura screenshot board con 4 columnas pobladas → guardar en `dod_evidence/board_screenshot.png`.
## Anti-scope
- Sin grafo de dependencias (epic 0130 lo describe como anti-scope v1).
- Sin crear issues nuevos (solo editar existentes).
- Sin edicion de body MD (solo frontmatter).
- Sin syntax highlighting markdown.
@@ -0,0 +1,90 @@
---
id: "0131"
title: "Modulo C++ chat_panel — panel ImGui para chat con agentes"
status: pendiente
type: app
domain:
- cpp-stack
- agents
- dev-ux
scope: cross-stack
priority: alta
depends:
- "0113"
blocks: []
related:
- "0130"
created: 2026-05-22
updated: 2026-05-22
tags: [cpp, imgui, agents, chat, module, sse]
flow: ""
---
# 0131 — Modulo C++ chat_panel
**Status:** pendiente
## Por que
Tras lanzar un agente desde kanban_cpp (issue 0130), no hay forma de interactuar con el desde la propia app. Hoy el flujo es: lanzar agente, abrir terminal aparte, `tail -f /tmp/wt-.../agent.log`. Queremos un panel C++ reutilizable que cualquier app embebra para chatear con un agente (Claude headless o futuros) y ver su output en streaming.
## Que entrega
Modulo `cpp/functions/viz/chat_panel/` (paquete del registry, kind: function, lang: cpp, domain: viz). API:
```cpp
namespace fn_chat {
struct ChatPanel {
// run_id del agent_runner_api; null = panel vacio "no agent attached"
std::string run_id;
std::string backend_url = "http://127.0.0.1:8486"; // agent_runner_api
bool auto_scroll = true;
};
void render(ChatPanel& panel);
}
```
Comportamiento:
- Conecta SSE `/api/runs/<run_id>/sse` en background thread (reusa `sse_client_cpp_core`).
- Parsea eventos `state`, `log`, `evidence`, `finish` y los renderiza:
- `log` → linea cruda en buffer scrollable.
- `state` → badge superior con status (`pending/running/done/aborted/failed`).
- `evidence` → chip lateral con kind + payload_url.
- `finish` → marca run terminada, deja conexion para ver historico.
- Input box inferior (multiline) + boton "Send". POST a `/api/runs/<run_id>/message` (endpoint A IMPLEMENTAR en agent_runner_api — extension paralela; si no existe, boton se deshabilita).
- Toolbar: `Abort run`, `Clear buffer`, `Show evidence panel`.
## Estructura
```
cpp/functions/viz/chat_panel/
chat_panel.h
chat_panel.cpp
chat_panel.md
chat_panel_test.cpp
```
## Reusa del registry
- `sse_client_cpp_core` — SSE async.
- `http_request_cpp_core` — POST mensajes / abort.
- `selectable_text_cpp_viz` — copy log lines.
- `data_table_cpp_viz` — opcional para tabla de evidencias.
## DoD
- Modulo compila en Linux + Windows.
- Demo en `primitives_gallery` o app dedicada `agent_chat_demo` con run_id fijo + mock SSE feeder.
- Integracion en kanban_cpp v2: nuevo panel "Chat" que se abre al hacer click en card con agent_active, run_id se pasa automatico.
- `e2e_checks`: smoke con mock SSE; assertion: tras emitir 3 eventos de log, panel los muestra en orden.
## Anti-scope (v1)
- No persiste history local (refresh = perdemos buffer; agent.log es la fuente).
- No syntax highlight markdown / codigo.
- Sin multi-run (un panel = un run).
- Sin file diff inline (kind:evidence con kind:diff queda como link a `git show`).
## Notas
Si el endpoint POST `/api/runs/:id/message` no existe en agent_runner_api, abrir issue paralelo `0131b` para anadirlo (claude headless aceptara mensajes via stdin del subprocess — el runner debe forwardearlos). Para v1 se acepta panel read-only.
@@ -0,0 +1,92 @@
---
id: "0132"
title: "Modulo C++ terminal_panel — emulador TTY ImGui embebible"
status: pendiente
type: app
domain:
- cpp-stack
- dev-ux
- apps-infra
scope: cross-stack
priority: alta
depends: []
blocks: []
related:
- "0130"
- "0131"
created: 2026-05-22
updated: 2026-05-22
tags: [cpp, imgui, terminal, pty, module, ausente-ready]
flow: ""
---
# 0132 — Modulo C++ terminal_panel
**Status:** pendiente
## Por que
Apps del ecosistema (kanban_cpp, services_monitor, agents_dashboard) necesitan ver output crudo de comandos shell sin abrir un terminal externo. Tipico: tail de un log, watch de un curl, ejecutar `git status` rapido. Solucion estandar: modulo `terminal_panel` reusable que arranca un shell hijo via PTY y lo renderiza en ImGui.
## Que entrega
Modulo `cpp/functions/viz/terminal_panel/`:
```cpp
namespace fn_term {
struct TerminalPanel {
std::string shell; // "/bin/bash" linux, "powershell.exe" windows; default auto
std::string cwd; // working dir; default = current
std::vector<std::string> env; // KEY=VAL extras
int scrollback_lines = 5000;
bool readonly = false; // true = no input forwarding (tail-only)
};
void open(TerminalPanel& panel); // crea proceso hijo + PTY
void render(TerminalPanel& panel);
void send(TerminalPanel& panel, const std::string& text); // stdin
void close(TerminalPanel& panel);
}
```
Implementacion:
- Linux: `forkpty` + `read/write` non-blocking en background thread.
- Windows: ConPTY (CreatePseudoConsole) + ReadFile en thread.
- Buffer circular `scrollback_lines` filas; render con `ImGui::TextUnformatted` por chunk para minimizar costo.
- Soporte minimo de ANSI: cursor pos, color FG/BG basico (16 colores), clear screen. NO soporte completo (no Vim, no top, no curses pesado).
- Toolbar: clear, copy selection, reset shell, scroll-to-bottom.
## Estructura
```
cpp/functions/viz/terminal_panel/
terminal_panel.h
terminal_panel.cpp
terminal_panel.md
terminal_panel_linux.cpp // forkpty path
terminal_panel_windows.cpp // ConPTY path
terminal_panel_test.cpp
```
## Reusa del registry
- `logger_cpp_core` (fn_log) — log errores spawn/io.
- `ansi_parser_cpp_core` — si existe, parsear secuencias ANSI. Si no, delegar a `fn-constructor` para crearlo dentro de este issue (sub-deliverable).
## DoD
- Compila Linux + Windows.
- Demo: `primitives_gallery` muestra terminal corriendo `bash -i` (linux) / `cmd.exe` (windows).
- Smoke test: spawn `echo hello && exit 0` → buffer contiene "hello".
- Integracion en kanban_cpp v2: panel "Logs" que toma `run_id` de issue activa y arranca `tail -f /tmp/wt-<slug>-<runid>/agent.log` (readonly=true).
- FPS sin caida bajo carga de `yes "x"` (saturado): 60fps target con scrollback truncado.
## Anti-scope (v1)
- Sin soporte completo ANSI (no italics, no 256 colores, no Unicode wide).
- Sin Vim / programas curses-pesados (cursor visible solo).
- Sin SSH remoto (solo shell local).
- Sin tabs multiples en un panel (un panel = un proceso).
## Notas
ConPTY requiere Windows 10 v1809+. Si target inferior, fallback a CreatePipe sin PTY (sin redimensionado).
@@ -0,0 +1,74 @@
---
id: "0133"
title: "data_table: optimizar para 10M filas sin caida de FPS (finalize modulo)"
status: pendiente
type: refactor
domain:
- cpp-stack
- data-ingest
scope: app-scoped
priority: alta
depends: []
blocks: []
related:
- "0081"
- "0097"
created: 2026-05-22
updated: 2026-05-22
tags: [cpp, imgui, performance, data_table, finalize]
flow: ""
---
# 0133 — data_table 10M rows sin caida FPS
**Status:** pendiente
## Por que
`data_table_cpp_viz` (modulo `fn_module_data_table` / `fn_table_viz`) actualmente maneja decenas de miles de filas con `ImGuiListClipper` y rinde bien. Apps reales (call_monitor con telemetria, services_monitor con escalado, futuro graph_explorer con nodos) ya nos llevan a millones de filas. Objetivo: cerrar el modulo con benchmark estable de **10M filas a >=60fps** en hardware tipico (Ryzen 5 / i5 8th gen + 16GB).
## Que entrega
Refactor del modulo manteniendo API publica + un benchmark suite.
### Cambios tecnicos
1. **Storage columnar** — hoy `std::vector<std::vector<Cell>>` row-major. Cambiar a column-major (`Column { type; vector<T> data }`) para localidad de cache + iteracion. Las celdas se materializan solo para las filas visibles.
2. **String interning** — columnas de tipo string usan tabla de strings global con `uint32_t` indices. 10M filas con 50% strings repetidas → ahorra 60-70% RAM.
3. **Lazy filter/sort indices** — en vez de re-ordenar el storage, mantener `vector<uint32_t> visible_rows` que apunta al storage subyacente. Filter/sort solo reescribe ese vector.
4. **Computed columns en bloques**`compute_stage_cpp_core` ahora corre por cell; cambiar a procesar bloques de 1024 filas con SIMD via `OpenMP` (ya esta linkeado en fn_framework).
5. **Render path**`ImGuiListClipper` sigue siendo el frontend, pero el callback de render no debe asignar memoria por fila. Pre-formatear strings de display en `column.display_cache[row_idx]` con LRU de 100k entradas; resto se formatea on-the-fly.
6. **Color rules**`data_table_color_rules_cpp_viz` se evalua hoy por celda visible. Cachear el rule_id resuelto por row_idx tras primer paint.
7. **Stats**`compute_column_stats_cpp_core` solo se recalcula cuando cambia el filtro, no cada frame.
### Benchmark suite
`cpp/apps/data_table_bench/`:
- Genera dataset sintetico 10M filas x 20 cols (mix int/float/string/timestamp).
- Mide FPS sostenido durante:
- scroll lineal full range (down → bottom).
- filter por string match (`LIKE %foo%`).
- sort por columna numerica.
- color rule `value > p95`.
- Output: `fps_p50`, `fps_p1`, `mem_rss_mb`, `cpu_pct`.
- Asercion DoD: `fps_p1 >= 60` en cada escenario.
## DoD
- Refactor entregado sin romper apps consumidoras (call_monitor, services_monitor, graph_explorer, navegator_dashboard, kanban_cpp future).
- Benchmark suite ejecutable: `./data_table_bench --rows 10000000 --duration 30`.
- Resultados de benchmark guardados en `apps/data_table_bench/operations.db` con assertion `fps_p1 >= 60`.
- `e2e_checks` corriendo benchmark con dataset reducido (100k filas) en CI; full bench manual.
- Modulo marcado `version: 1.0.0` y `tags: [stable]` en su `.md`.
- Guia "porting old call sites" si la API publica cambia (en `cpp/functions/viz/data_table/MIGRATION.md`).
## Anti-scope
- Sin GPU rendering (sigue siendo CPU + ImGui).
- Sin paginacion remota (sigue todo in-memory).
- Sin streaming append-while-rendering (snapshot al frame inicio).
- Sin virtualizacion horizontal (todas las cols se renderizan; assumed N_cols <= 100).
## Notas
Issue 0081 introdujo la migracion inline → modulo. Issue 0097 cerro el wrapping en fn_module/fn_table_viz. Esta issue es el **finalize**: lo deja `1.0.0` con benchmark + suficiente performance para que las apps de telemetria/graph no necesiten paginar manual.