Files
egutierrez 3a8a75f205 chore: auto-commit (2 archivos)
- app.md
- appicon.ico

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:31:31 +02:00

6.8 KiB

name, lang, domain, version, description, tags, uses_functions, uses_types, framework, entry_point, dir_path, repo_url, icon
name lang domain version description tags uses_functions uses_types framework entry_point dir_path repo_url icon
altsnap_jitter_test cpp tools 0.1.0 Regression test for multi-viewport window jitter + iconified survival + Alt+RMB resize + Alt+LMB move. Six phases: p1 main-window sync, p2 AltSnap on main HWND, p3 AltSnap on secondary viewport HWND, p4 iconify+restore preserves floating panels, p5 Alt+RMB consumed by WndProc, p6 Alt+LMB consumed by WndProc.
imgui
test
regression
headless
imgui main.cpp apps/altsnap_jitter_test
phosphor accent
arrows-clockwise #dc2626

altsnap_jitter_test

Headless C++ harness para validar el subclass anti-jitter del framework (cpp/framework/app_base.cpp): movimiento/redimensionado externos sin temblor en la ventana principal y en viewports secundarios, iconified main no pierde paneles flotantes, Alt+RMB resize anywhere, Alt+LMB move anywhere.

Que hace

Seis fases en una sola sesion de fn::run_app:

  1. p1.sync (cross-platform). Warmup 8 frames, mueve la ventana principal via glfwSetWindowPos un step por frame durante 60 frames. Muestrea pos OS (glfwGetWindowPos) vs pos ImGui (GetMainViewport()->Pos). Tolerancia 1px. Cuenta divergencias = bad_sync.
  2. p2.altsnap (Windows). Worker thread fakea WM_ENTERSIZEMOVE + burst de 30 SetWindowPos(SWP_ASYNCWINDOWPOS) + WM_EXITSIZEMOVE sobre el HWND principal. Aserta renders_during==0 (subclass del WndProc principal y gate del main loop activos).
  3. p3.secondary (Windows). Fuerza creacion de un viewport secundario (panel flotante, ConfigViewportsNoAutoMerge=true), localiza su HWND en pio.Viewports via find_secondary_viewport_hwnd, y repite el bracket sobre el. Valida que el subclass per-frame cubre tambien el HWND secundario.
  4. p4.minimize (Windows). State machine 4 steps con kP4SettleFrames=20 entre cada uno. Captura IsWindow(secondary_hwnd) antes/durante/despues de glfwIconifyWindow + glfwRestoreWindow. Asserta los 3 estados vivos AND renders_iconified > 0 (frame loop sigue activo durante iconify para que los flotantes no se pierdan).
  5. p5.alt_rmb (Windows). fn::internal::set_force_alt_for_test(true) + SendMessageW(hwnd, WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(40,40)) sincrono mismo-hilo. Asserta fn::internal::alt_rmb_resize_count() incrementa en 1. En test mode el handler salta el PostMessage SC_SIZE para no atrapar al harness en modal.
  6. p6.alt_lmb (Windows). Mismo patron para WM_LBUTTONDOWN. Asserta fn::internal::alt_lmb_move_count() incrementa en 1.

PASS = todas las fases con sus deltas positivos. SKIP en Linux para p2-p6.

Run

WSL/Linux (sanity build; p2-p6 skipped):

cd cpp && cmake -B build/linux -DFN_BUILD_TESTS=OFF
cmake --build build/linux --target altsnap_jitter_test -j4
xvfb-run -a -s "-screen 0 1280x800x24" \
  env LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe \
  build/linux/apps/altsnap_jitter_test/altsnap_jitter_test
echo "EXIT: $?"

Windows (cross-compile + Desktop deploy + run):

source bash/functions/infra/e2e_run_cpp_windows.sh
e2e_run_cpp_windows altsnap_jitter_test

Output esperado (PASS, Windows)

[p1.sync] DONE frames=60 max_sync=0px max_clamp=0px bad_sync=0 bad_clamp=0
[p2.altsnap] DONE renders before=N during=0 after=M
[p3.secondary] DONE renders before=N during=0 after=M
[p4.minimize] DONE alive(before=1 during=1 after=1) renders_iconified=20
[p5.alt_rmb] DONE alt_resize delta=1 (after=1) sizemove_enter delta=0 (after=2)
[p6.alt_lmb] DONE alt_move delta=1 (after=1)
[altsnap_jitter] p1=PASS p2=PASS p3=PASS p4=PASS p5=PASS p6=PASS overall=PASS

En Linux/xvfb p2-p6 reportan SKIPPED. P1 puede mostrar lag pre-existente bajo xvfb por como X procesa SetWindowPos; no es un fallo del fix.

Criterio de fallo

  • p1_bad_sync > 0: ImGui viewport->Pos quedo fuera de sincronia con la pos OS. Roto el callback GLFW + per-frame sync.
  • p2_renders_during > 0: la app sigue dibujando durante un bracket AltSnap en el HWND principal. Roto el subclass del WndProc principal o la gate del main loop.
  • p3_renders_during > 0: la app sigue dibujando durante un bracket AltSnap en un HWND secundario (panel flotante). Roto el escaneo per-frame de pio.Viewports que instala el subclass en cada platform window.
  • p4 alive(during)=0: floating panel se cierra/desaparece al minimizar el main. Regresion del fix iconified+secondary.
  • p4 renders_iconified == 0: el iconified-gate volvio a glfwWaitEvents+continue global sin chequear secondaries. Floating panels se congelarian.
  • p5 alt_resize delta == 0: Alt+RMB no se consume. Subclass no esta en el chain (ImGui_ImplGlfw_WndProc capturo nuestra WndProc como prev y no chainea bien) o flag force_alt_for_test no se aplica.
  • p6 alt_move delta == 0: misma raiz que p5 pero para LMB.

Donde vive el fix

cpp/framework/app_base.cpp:

  • GLFW pos/size callbacks (anti-jitter capa 1).
  • Per-frame viewport sync (capa 2).
  • unordered_map<HWND, WNDPROC> g_subclassed + per-frame install_sizemove_subclass_hwnd sobre pio.Viewports (capa 3, multi-HWND).
  • Iconified gate detecta secondary viewports y fall-through si existen.
  • WM_LBUTTONDOWN/WM_RBUTTONDOWN Alt-held → WM_SYSCOMMAND, SC_MOVE|HTCAPTION / SC_SIZE|dir.
  • io.ConfigWindowsMoveFromTitleBarOnly = true (floating panels respetan header-only).
  • fn::internal::* counters expuestos para tests headless.

cpp/framework/app_base.h:

  • namespace fn::internal { sizemove_enter_count(); alt_rmb_resize_count(); alt_lmb_move_count(); rbuttondown_seen_count(); set_force_alt_for_test(bool); }.

Notas

  • keybd_event(VK_MENU) NO es fiable para drivear GetAsyncKeyState desde tests headless cross-compilados — la sesion de input del proceso no esta foreground. Usa set_force_alt_for_test(true) + SendMessageW sincrono mismo-hilo. Bypassa kernel-input filter (que dropea silenciosamente PostMessage(WM_RBUTTONDOWN) sintetizado).
  • ImGui_ImplGlfw subclassea el HWND despues que nosotros (vendor cpp/vendor/imgui/backends/imgui_impl_glfw.cpp linea ~820, captura bd->PrevWndProc = our_subclass). Por eso ImGui llama a nuestro WndProc via CallWindowProc(prev_wndproc, ...) y todos los mensajes nos llegan en orden correcto. No re-subclassear (provoca recursion infinita via cycle).
  • Test mode (set_force_alt_for_test(true)) hace que el WndProc cuente pero NO postee SC_SIZE/SC_MOVE — evita quedarse atrapado en modal sizemove. La parte "entrar al modal" se valida por p2/p3 fakeando WM_ENTERSIZEMOVE directamente.

Capability growth log

Una linea por bump SemVer. Bump-type segun .claude/commands/version.md:

  • major: breaking observable (CLI args, schema BBDD propia, formato wire).

  • minor: feature aditiva (nuevo panel, endpoint, opcion).

  • patch: bugfix sin cambio observable.

  • v0.1.0 (2026-05-18) — baseline.