diff --git a/CMakeLists.txt b/CMakeLists.txt index 602225a..f25a8d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,10 +33,10 @@ target_link_libraries(navegator_dashboard PRIVATE 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. -if(TARGET fn_table_viz) - target_link_libraries(navegator_dashboard PRIVATE fn_table_viz) +if(TARGET fn_module_data_table) + target_link_libraries(navegator_dashboard PRIVATE fn_module_data_table) endif() set_target_properties(navegator_dashboard PROPERTIES WIN32_EXECUTABLE TRUE) @@ -65,8 +65,8 @@ if(FN_BUILD_TESTS) ws2_32 imgui_node_editor ) - if(TARGET fn_table_viz) - target_link_libraries(navegator_dashboard_tests PRIVATE fn_table_viz) + if(TARGET fn_module_data_table) + target_link_libraries(navegator_dashboard_tests PRIVATE fn_module_data_table) endif() # Excluye int main() de main.cpp; el harness define su propio main(). target_compile_definitions(navegator_dashboard_tests PRIVATE FN_TEST_BUILD) diff --git a/app.md b/app.md index ce0e9a5..605affd 100644 --- a/app.md +++ b/app.md @@ -26,10 +26,14 @@ uses_functions: - infer_json_rows_schema_py_core - cdp_pick_element_js_js_browser uses_types: [] +uses_modules: [data_table_cpp] framework: "imgui" entry_point: "main.cpp" dir_path: "projects/navegator/apps/navegator_dashboard" repo_url: "" +icon: + phosphor: "compass" + accent: "#2563eb" e2e_checks: - id: build_windows diff --git a/autoextract_panel.cpp b/autoextract_panel.cpp index 7cd33b7..6276f4b 100644 --- a/autoextract_panel.cpp +++ b/autoextract_panel.cpp @@ -16,6 +16,7 @@ #include "session_state.h" #include "py_subprocess.h" #include "picker_state.h" +#include "app_base.h" #include "crude_json.h" @@ -123,6 +124,9 @@ try: out = {"tab_id": tab_id} if isinstance(schema, dict): 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: out["fields"] = schema print(json.dumps(out)) @@ -141,6 +145,16 @@ except Exception as e: std::lock_guard lk(g_ax.mu); 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()) { std::lock_guard lk(g_ax.mu); g_ax.last_error = r.error.empty() ? "python exited non-zero" : r.error; diff --git a/panels.cpp b/panels.cpp index 9c71be9..dccc219 100644 --- a/panels.cpp +++ b/panels.cpp @@ -13,7 +13,7 @@ #include "core/icons_tabler.h" #include "core/tokens.h" #include "core/data_table_types.h" -#include "viz/data_table.h" +#include "data_table/data_table.h" #include "chrome_scanner.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 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(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) --- 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 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(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) --- ImGui::Separator(); @@ -1301,7 +1318,15 @@ void render_network_panel(bool* p_open) { cs.duration_error_ms = 5000.0f; } - data_table::render("##dt_requests", {req_tbl}, g_net_ui.dt_state, true); + std::vector 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(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 // (handled inside data_table via row-click; URL copy available via right-click diff --git a/py_subprocess.cpp b/py_subprocess.cpp index 19d20f7..a1e66d2 100644 --- a/py_subprocess.cpp +++ b/py_subprocess.cpp @@ -68,6 +68,9 @@ std::string py_resolve_interpreter() { #ifdef _WIN32 std::string venv_py = root + "\\python\\.venv\\Scripts\\python.exe"; 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 std::string venv_py = root + "/python/.venv/bin/python3"; if (file_exists(venv_py)) return venv_py; @@ -108,10 +111,36 @@ PyResult py_run(const std::vector& argv, int timeout_ms) { PyResult 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 -- env FN_REGISTRY_ROOT= python3 -c "..." + // 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 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; - for (size_t i = 0; i < argv.size(); ++i) { + for (size_t i = 0; i < final_argv.size(); ++i) { if (i) cmd += ' '; - cmd += quote_arg_win(argv[i]); + cmd += quote_arg_win(final_argv[i]); } HANDLE r_pipe = nullptr; diff --git a/py_subprocess.h b/py_subprocess.h index af5ec97..655b8b5 100644 --- a/py_subprocess.h +++ b/py_subprocess.h @@ -6,8 +6,18 @@ // // Decisiones: // - Heredoc inline: el script Python se pasa via -c "" para evitar archivos temporales. -// - PATH: usa "python3" o "python". Fallback: ${FN_REGISTRY_ROOT}/python/.venv/Scripts/python.exe -// (Windows venv layout) o /python/.venv/bin/python3 (POSIX). +// - PATH: prioridad del interprete: +// 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 -- env +// FN_REGISTRY_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. // - Stderr: redirigido a stdout para facilitar diagnostico (logs visibles). // - Sin consola visible en Windows (CREATE_NO_WINDOW). @@ -25,11 +35,15 @@ struct PyResult { std::string error; // mensaje propio si CreateProcess/popen fallo }; -// Devuelve la ruta al interprete python a usar. Prioridad: -// 1. ${FN_REGISTRY_ROOT}/python/.venv/Scripts/python.exe (Windows) -// 2. ${FN_REGISTRY_ROOT}/python/.venv/bin/python3 (POSIX/MinGW) -// 3. "python3" en PATH -// 4. "python" en PATH (Windows default) +// Devuelve la ruta al interprete python a usar. Prioridad (Windows): +// 1. ${FN_REGISTRY_ROOT}\python\.venv\Scripts\python.exe — venv Windows nativo +// 2. "wsl.exe" (sentinel) — cuando FN_REGISTRY_ROOT_WSL esta seteado. +// py_run() lo expande a: wsl.exe --cd -- env FN_REGISTRY_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(); // Devuelve FN_REGISTRY_ROOT. Si no esta seteada, intenta deducirla: