From 86825d0b15e3a6364ab96821050979157b633c57 Mon Sep 17 00:00:00 2001 From: arzafran Date: Thu, 30 Jul 2026 20:51:34 -0300 Subject: [PATCH] ci: measure the text-input refresh cost on demand #219 added an in-process sampler for the refresh call after each printable keystroke, but nothing ran it -- settling #183's remaining half still needed someone to build a tagged app locally and drive it by hand. The tests-build-and-lag job already builds the app, spins a virtual display, launches it tagged and waits on the socket. That is everything the sampler harness needs, so this adds a dispatch-only step that reuses it. Its own app instance, deliberately: the sampler only records when PROGRAMA_TYPING_TIMING_LOGS=1, and enabling that for the lag harness above would add logging to the very path that harness measures. Reports numbers into the run summary and asserts no budget. There is no baseline yet, and a threshold invented before any data would be a number from nowhere. With this, #183's remaining half becomes decidable without a local run: dispatch on main, dispatch on perf/183-redraw-after-input, compare p95 at microsecond resolution rather than through a socket round trip that cannot resolve it. Refs #183 --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9d4f259..897247ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -691,6 +691,52 @@ jobs: PROGRAMA_LAG_KEY_COMBO="${{ inputs.lag_combo || 'up' }}" \ python3 tests/test_workspace_churn_up_arrow_lag.py + # On-demand only. This measures the refresh call itself with an in-process + # timer rather than an end-to-end socket round trip, because that round trip + # is order-milliseconds and cannot resolve a change of this size -- four runs + # of the forceRefresh change produced completely overlapping ranges (#183). + # + # Runs in its own app instance: the sampler only records when + # PROGRAMA_TYPING_TIMING_LOGS=1, and enabling that for the lag harness above + # would add logging to the very path that harness measures. + # + # Reports numbers, asserts no budget. There is no baseline yet, and a + # threshold invented before any data would be a number from nowhere. + - name: Measure text-input refresh cost (dispatch only) + if: github.event_name == 'workflow_dispatch' + run: | + set -euo pipefail + + APP="$(find "$HOME/Library/Developer/Xcode/DerivedData" -path "*/Build/Products/Debug/Programa DEV.app" -print -quit)" + [ -n "$APP" ] || { echo "Programa DEV.app not found" >&2; exit 1; } + + TAG="ci-refresh-cost" + SOCK="/tmp/programa-debug-${TAG}.sock" + BUNDLE_ID="$( + /usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP/Contents/Info.plist" 2>/dev/null \ + || echo 'com.darkroom.programa.debug' + )" + + pkill -x "Programa DEV" || true + rm -f "$SOCK" || true + defaults write "$BUNDLE_ID" socketControlMode -string full >/dev/null 2>&1 || true + + PROGRAMA_TAG="$TAG" PROGRAMA_SOCKET_PATH="$SOCK" PROGRAMA_UI_TEST_MODE=1 \ + PROGRAMA_TYPING_TIMING_LOGS=1 \ + "$APP/Contents/MacOS/Programa DEV" >/tmp/programa-ci-refresh-cost.log 2>&1 & + APP_PID=$! + trap 'kill "$APP_PID" >/dev/null 2>&1 || true' EXIT + + for _ in {1..240}; do + [ -S "$SOCK" ] && break + sleep 0.25 + done + [ -S "$SOCK" ] || { echo "Socket not ready at $SOCK" >&2; exit 1; } + + PROGRAMA_SOCKET_PATH="$SOCK" \ + PROGRAMA_REFRESH_COST_KEY_EVENTS=400 \ + python3 tests/test_text_input_refresh_cost.py | tee -a "$GITHUB_STEP_SUMMARY" + - name: Cleanup virtual display if: always() run: |