docs(flows): DoD obligatorio con user-facing surface + abrir issues 0100-0103 (taxonomia, frontmatter migration, dev_console, work dashboard)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 00:07:04 +02:00
parent 8357774b86
commit 4164f5adfc
6 changed files with 104 additions and 18 deletions
+5 -5
View File
@@ -33,10 +33,10 @@ target_link_libraries(navegator_dashboard PRIVATE
imgui_node_editor imgui_node_editor
) )
# fn_table_viz: provides data_table::render(), viz_render, TQL engine, Lua, LLM. # fn_module_data_table: provides data_table::render(), viz_render, TQL engine, Lua, LLM.
# Guard keeps the app compilable in builds where vendor/lua is absent. # Guard keeps the app compilable in builds where vendor/lua is absent.
if(TARGET fn_table_viz) if(TARGET fn_module_data_table)
target_link_libraries(navegator_dashboard PRIVATE fn_table_viz) target_link_libraries(navegator_dashboard PRIVATE fn_module_data_table)
endif() endif()
set_target_properties(navegator_dashboard PROPERTIES WIN32_EXECUTABLE TRUE) set_target_properties(navegator_dashboard PROPERTIES WIN32_EXECUTABLE TRUE)
@@ -65,8 +65,8 @@ if(FN_BUILD_TESTS)
ws2_32 ws2_32
imgui_node_editor imgui_node_editor
) )
if(TARGET fn_table_viz) if(TARGET fn_module_data_table)
target_link_libraries(navegator_dashboard_tests PRIVATE fn_table_viz) target_link_libraries(navegator_dashboard_tests PRIVATE fn_module_data_table)
endif() endif()
# Excluye int main() de main.cpp; el harness define su propio main(). # Excluye int main() de main.cpp; el harness define su propio main().
target_compile_definitions(navegator_dashboard_tests PRIVATE FN_TEST_BUILD) target_compile_definitions(navegator_dashboard_tests PRIVATE FN_TEST_BUILD)
+4
View File
@@ -26,10 +26,14 @@ uses_functions:
- infer_json_rows_schema_py_core - infer_json_rows_schema_py_core
- cdp_pick_element_js_js_browser - cdp_pick_element_js_js_browser
uses_types: [] uses_types: []
uses_modules: [data_table_cpp]
framework: "imgui" framework: "imgui"
entry_point: "main.cpp" entry_point: "main.cpp"
dir_path: "projects/navegator/apps/navegator_dashboard" dir_path: "projects/navegator/apps/navegator_dashboard"
repo_url: "" repo_url: ""
icon:
phosphor: "compass"
accent: "#2563eb"
e2e_checks: e2e_checks:
- id: build_windows - id: build_windows
+14
View File
@@ -16,6 +16,7 @@
#include "session_state.h" #include "session_state.h"
#include "py_subprocess.h" #include "py_subprocess.h"
#include "picker_state.h" #include "picker_state.h"
#include "app_base.h"
#include "crude_json.h" #include "crude_json.h"
@@ -123,6 +124,9 @@ try:
out = {"tab_id": tab_id} out = {"tab_id": tab_id}
if isinstance(schema, dict): if isinstance(schema, dict):
out.update(schema) out.update(schema)
# llm_propose_scraping_schema returns "schema" key; remap to "fields" for parser.
if "schema" in schema and "fields" not in schema:
out["fields"] = schema["schema"]
else: else:
out["fields"] = schema out["fields"] = schema
print(json.dumps(out)) print(json.dumps(out))
@@ -141,6 +145,16 @@ except Exception as e:
std::lock_guard<std::mutex> lk(g_ax.mu); std::lock_guard<std::mutex> lk(g_ax.mu);
g_ax.raw_python_output = r.stdout_data; g_ax.raw_python_output = r.stdout_data;
} }
// Debug dump: stdout + diagnostic context to disk for offline inspection.
{
FILE* f = std::fopen(fn::local_path("autoextract_last.txt"), "wb");
if (f) {
std::fprintf(f, "EXIT=%d ERR=%s\n", r.exit_code, r.error.c_str());
std::fprintf(f, "STDOUT_LEN=%zu\n--- STDOUT ---\n", r.stdout_data.size());
std::fwrite(r.stdout_data.data(), 1, r.stdout_data.size(), f);
std::fclose(f);
}
}
if (r.exit_code != 0 || r.stdout_data.empty()) { if (r.exit_code != 0 || r.stdout_data.empty()) {
std::lock_guard<std::mutex> lk(g_ax.mu); std::lock_guard<std::mutex> lk(g_ax.mu);
g_ax.last_error = r.error.empty() ? "python exited non-zero" : r.error; g_ax.last_error = r.error.empty() ? "python exited non-zero" : r.error;
+29 -4
View File
@@ -13,7 +13,7 @@
#include "core/icons_tabler.h" #include "core/icons_tabler.h"
#include "core/tokens.h" #include "core/tokens.h"
#include "core/data_table_types.h" #include "core/data_table_types.h"
#include "viz/data_table.h" #include "data_table/data_table.h"
#include "chrome_scanner.h" #include "chrome_scanner.h"
#include "chrome_launcher.h" #include "chrome_launcher.h"
@@ -230,7 +230,14 @@ void render_browsers_panel(bool* p_open) {
}; };
} }
data_table::render("##dt_browsers", {tbl}, g_browsers.dt_state, false); std::vector<data_table::TableEvent> dt_events;
data_table::render("##dt_browsers", {tbl}, g_browsers.dt_state, &dt_events, /*show_chrome=*/false);
for (auto& ev : dt_events) {
if (ev.kind == data_table::TableEventKind::RowDoubleClick &&
ev.row >= 0 && ev.row < static_cast<int>(g_browsers.instances.size())) {
g_session().select_browser(g_browsers.instances[ev.row].port);
}
}
// --- Actions: inline button list per row (no BeginTable — Button renderer not Fase-1) --- // --- Actions: inline button list per row (no BeginTable — Button renderer not Fase-1) ---
ImGui::Separator(); ImGui::Separator();
@@ -445,7 +452,17 @@ void render_tabs_panel(bool* p_open) {
}; };
} }
data_table::render("##dt_tabs", {tbl}, g_tabs_ui.dt_state, false); std::vector<data_table::TableEvent> dt_events;
data_table::render("##dt_tabs", {tbl}, g_tabs_ui.dt_state, &dt_events, /*show_chrome=*/false);
for (auto& ev : dt_events) {
if (ev.kind == data_table::TableEventKind::RowDoubleClick &&
ev.row >= 0 && ev.row < static_cast<int>(visible_tabs.size())) {
const CdpTab* tp = visible_tabs[ev.row];
if (tp && !tp->ws_url.empty()) {
g_session().select_tab(tp->id, tp->ws_url);
}
}
}
// --- Actions: inline button list per visible row (no BeginTable — Button renderer not Fase-1) --- // --- Actions: inline button list per visible row (no BeginTable — Button renderer not Fase-1) ---
ImGui::Separator(); ImGui::Separator();
@@ -1301,7 +1318,15 @@ void render_network_panel(bool* p_open) {
cs.duration_error_ms = 5000.0f; cs.duration_error_ms = 5000.0f;
} }
data_table::render("##dt_requests", {req_tbl}, g_net_ui.dt_state, true); std::vector<data_table::TableEvent> dt_events;
data_table::render("##dt_requests", {req_tbl}, g_net_ui.dt_state, &dt_events, /*show_chrome=*/true);
for (auto& ev : dt_events) {
if (ev.kind == data_table::TableEventKind::RowDoubleClick &&
ev.row >= 0 && ev.row < static_cast<int>(filtered.size())) {
g_net_ui.selected_id = filtered[ev.row]->id;
g_net_ui.selected_index = ev.row;
}
}
// Context menu + selection: track clicked row by matching Name col // Context menu + selection: track clicked row by matching Name col
// (handled inside data_table via row-click; URL copy available via right-click // (handled inside data_table via row-click; URL copy available via right-click
+31 -2
View File
@@ -68,6 +68,9 @@ std::string py_resolve_interpreter() {
#ifdef _WIN32 #ifdef _WIN32
std::string venv_py = root + "\\python\\.venv\\Scripts\\python.exe"; std::string venv_py = root + "\\python\\.venv\\Scripts\\python.exe";
if (file_exists(venv_py)) return venv_py; if (file_exists(venv_py)) return venv_py;
// Windows venv no encontrado — intentar via WSL si FN_REGISTRY_ROOT_WSL existe.
std::string wsl_root = getenv_str("FN_REGISTRY_ROOT_WSL");
if (!wsl_root.empty()) return "wsl.exe"; // sentinel; py_run lo expande
#else #else
std::string venv_py = root + "/python/.venv/bin/python3"; std::string venv_py = root + "/python/.venv/bin/python3";
if (file_exists(venv_py)) return venv_py; if (file_exists(venv_py)) return venv_py;
@@ -108,10 +111,36 @@ PyResult py_run(const std::vector<std::string>& argv, int timeout_ms) {
PyResult res; PyResult res;
if (argv.empty()) { res.error = "argv empty"; return res; } if (argv.empty()) { res.error = "argv empty"; return res; }
// Si argv[0] es el sentinel "wsl.exe", reescribir el comando para invocar
// el python del venv WSL con el contexto Linux correcto:
// wsl.exe --cd <linux_root> -- env FN_REGISTRY_ROOT=<linux_root> python3 -c "..." <args>
// Esto garantiza que el script Python recibe FN_REGISTRY_ROOT como path Linux,
// puede importar funciones del registry y resuelve deps del venv WSL.
std::vector<std::string> final_argv;
if (argv[0] == "wsl.exe") {
std::string wsl_root = getenv_str("FN_REGISTRY_ROOT_WSL");
if (wsl_root.empty()) {
res.error = "wsl.exe sentinel but FN_REGISTRY_ROOT_WSL not set";
return res;
}
std::string python3 = wsl_root + "/python/.venv/bin/python3";
final_argv.push_back("wsl.exe");
final_argv.push_back("--cd");
final_argv.push_back(wsl_root);
final_argv.push_back("--");
final_argv.push_back("env");
final_argv.push_back("FN_REGISTRY_ROOT=" + wsl_root);
final_argv.push_back(python3);
// Append rest of original argv (skip argv[0] = "wsl.exe")
for (size_t i = 1; i < argv.size(); ++i) final_argv.push_back(argv[i]);
} else {
final_argv = argv;
}
std::string cmd; std::string cmd;
for (size_t i = 0; i < argv.size(); ++i) { for (size_t i = 0; i < final_argv.size(); ++i) {
if (i) cmd += ' '; if (i) cmd += ' ';
cmd += quote_arg_win(argv[i]); cmd += quote_arg_win(final_argv[i]);
} }
HANDLE r_pipe = nullptr; HANDLE r_pipe = nullptr;
+21 -7
View File
@@ -6,8 +6,18 @@
// //
// Decisiones: // Decisiones:
// - Heredoc inline: el script Python se pasa via -c "<inline>" para evitar archivos temporales. // - Heredoc inline: el script Python se pasa via -c "<inline>" para evitar archivos temporales.
// - PATH: usa "python3" o "python". Fallback: ${FN_REGISTRY_ROOT}/python/.venv/Scripts/python.exe // - PATH: prioridad del interprete:
// (Windows venv layout) o /python/.venv/bin/python3 (POSIX). // Windows: (1) ${FN_REGISTRY_ROOT}\python\.venv\Scripts\python.exe si existe,
// (2) wsl.exe sentinel si FN_REGISTRY_ROOT_WSL esta seteado (invoca
// el python del venv WSL con: wsl.exe --cd <linux_root> -- env
// FN_REGISTRY_ROOT=<linux_root> python/.venv/bin/python3 ...),
// (3) "python" en PATH (sistema).
// POSIX: ${FN_REGISTRY_ROOT}/python/.venv/bin/python3, luego "python3".
// - Cuando se usa el sentinel wsl.exe, py_run reescribe el argv completo para que
// el script Python reciba FN_REGISTRY_ROOT como path Linux, pueda importar
// funciones del registry y use el venv WSL con todas las deps.
// - FN_REGISTRY_ROOT y FN_REGISTRY_ROOT_WSL se propagan via launch_cpp_app_windows
// (bash/functions/infra/launch_cpp_app_windows.sh v1.1.0).
// - Stdout: capturado completo. El llamante parsea JSON. // - Stdout: capturado completo. El llamante parsea JSON.
// - Stderr: redirigido a stdout para facilitar diagnostico (logs visibles). // - Stderr: redirigido a stdout para facilitar diagnostico (logs visibles).
// - Sin consola visible en Windows (CREATE_NO_WINDOW). // - Sin consola visible en Windows (CREATE_NO_WINDOW).
@@ -25,11 +35,15 @@ struct PyResult {
std::string error; // mensaje propio si CreateProcess/popen fallo std::string error; // mensaje propio si CreateProcess/popen fallo
}; };
// Devuelve la ruta al interprete python a usar. Prioridad: // Devuelve la ruta al interprete python a usar. Prioridad (Windows):
// 1. ${FN_REGISTRY_ROOT}/python/.venv/Scripts/python.exe (Windows) // 1. ${FN_REGISTRY_ROOT}\python\.venv\Scripts\python.exe — venv Windows nativo
// 2. ${FN_REGISTRY_ROOT}/python/.venv/bin/python3 (POSIX/MinGW) // 2. "wsl.exe" (sentinel) — cuando FN_REGISTRY_ROOT_WSL esta seteado.
// 3. "python3" en PATH // py_run() lo expande a: wsl.exe --cd <linux_root> -- env FN_REGISTRY_ROOT=<linux_root>
// 4. "python" en PATH (Windows default) // <linux_root>/python/.venv/bin/python3 ...
// 3. "python" — fallback PATH (python.exe del sistema, sin deps del registry)
// Prioridad (POSIX):
// 1. ${FN_REGISTRY_ROOT}/python/.venv/bin/python3
// 2. "python3" en PATH
std::string py_resolve_interpreter(); std::string py_resolve_interpreter();
// Devuelve FN_REGISTRY_ROOT. Si no esta seteada, intenta deducirla: // Devuelve FN_REGISTRY_ROOT. Si no esta seteada, intenta deducirla: