diff --git a/docs/superpowers/evidence/android-ui-vd-keyboard-2026-09-27.json b/docs/superpowers/evidence/android-ui-vd-keyboard-2026-09-27.json deleted file mode 100644 index ecf47076..00000000 --- a/docs/superpowers/evidence/android-ui-vd-keyboard-2026-09-27.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "intent": "Two Claude sessions sharing one phone must not disturb each other: a keyboard open on the real screen must never make android_ui press BACK on another session's virtual display.", - "scope": [ - "python_pkg/android_ui/_text_entry.py (keyboard_is_up only)", - "Non-goal: no change to real-screen (display 0 / no display) behaviour, which still checks and dismisses the IME" - ], - "changes": [ - "keyboard_is_up returns False when the driver targets a virtual display (display not None and not 0), so dismiss_keyboard sends no ESCAPE/BACK there", - "Test: a VD driver with the phone-wide keyboard shown runs no adb command at all" - ], - "verification": [ - { - "command": "adb shell dumpsys input_method (live phone, 2026-09-27)", - "result": "pass", - "evidence": "every IME binding reads imeDisplayId=0, including one whose targetWin was com.kuhy.workout_app.sandbox on targetDisplayId=13: the IME is drawn on display 0 for VD windows" - }, - { - "command": "python -m python_pkg.android_ui --display self tap ... (before the fix, while another session typed on the real screen)", - "result": "fail", - "evidence": "'the soft keyboard is still covering the screen after ESCAPE and BACK'; the BACK popped the workout route, a second run closed the app on the VD" - }, - { - "command": "same taps after the fix", - "result": "pass", - "evidence": "tapped 'Dumbbell Lunge progression settings' / 'Warmup set' / 'Wins needed down' on the VD with the other session still active" - }, - { - "command": "pytest python_pkg/android_ui --cov=python_pkg/android_ui --cov-branch", - "result": "pass", - "evidence": "87 passed; _text_entry.py 100% lines and branches" - } - ], - "risks": [ - "A future Android build that draws the IME on the VD itself would make taps under it land on keys; the phone would have to show imeDisplayId= for that, which it does not today" - ], - "rollback": [ - "git revert this commit", - "After rollback, VD taps work again only while no other session has a keyboard open on the real screen" - ] -} diff --git a/docs/superpowers/evidence/publish-caddy-site-2026-11-02.json b/docs/superpowers/evidence/publish-caddy-site-2026-11-02.json new file mode 100644 index 00000000..35079b6d --- /dev/null +++ b/docs/superpowers/evidence/publish-caddy-site-2026-11-02.json @@ -0,0 +1,28 @@ +{ + "intent": "Publish a loopback service at .kuhy.duckdns.org through the existing gitea-caddy edge with one validated command.", + "scope": [ + "linux_configuration/features/publish_caddy_site.sh (new)", + "Non-goal: no change to the live Caddy config, setup_gitea.sh or DNS" + ], + "changes": [ + "Writes sites/.kuhy.duckdns.org.caddy (reverse_proxy to 127.0.0.1:, optional bearer gate), validates on a tmp copy in a throwaway caddy:2.8 container, then reloads gitea-caddy" + ], + "verification": [ + { + "command": "shellcheck linux_configuration/features/publish_caddy_site.sh", + "result": "pass", + "evidence": "exit 0" + }, + { + "command": "publish_caddy_site.sh todo 8799 --dry-run (with and without a token file)", + "result": "pass", + "evidence": "caddy validate: Valid configuration, exit 0; live edge not modified" + } + ], + "risks": [ + "A real run reloads the shared edge; a bad snippet is caught by the pre-validation, but a reload still briefly touches all sites" + ], + "rollback": [ + "Delete ~/services/gitea/sites/.kuhy.duckdns.org.caddy and run docker exec gitea-caddy caddy reload --config /etc/caddy/Caddyfile" + ] +} diff --git a/linux_configuration/features/publish_caddy_site.sh b/linux_configuration/features/publish_caddy_site.sh new file mode 100755 index 00000000..7db37a93 --- /dev/null +++ b/linux_configuration/features/publish_caddy_site.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Publish a loopback service at https://.kuhy.duckdns.org through the +# existing Caddy edge (container gitea-caddy, host network, ports 80/443, +# HTTP-01 certs; see setup_gitea.sh). No sudo: the user is in the docker group. +# +# publish_caddy_site.sh # write snippet, validate, reload +# publish_caddy_site.sh --dry-run # validate only, change nothing +# +# Optional bearer gate: if ~/.config/-mcp/token exists (or TOKEN_FILE), +# requests without "Authorization: Bearer " get 401 at the edge. +# DNS: *.kuhy.duckdns.org already resolves to the home IP; nothing to add. +set -euo pipefail + +readonly SITES_DIR="${SITES_DIR:-${HOME}/services/gitea/sites}" +readonly CONTAINER="${CONTAINER:-gitea-caddy}" +readonly IMAGE="${CADDY_IMAGE:-caddy:2.8}" + +die() { + echo "Error: $*" >&2 + exit 1 +} + +[[ $# -ge 2 ]] || die "usage: $0 [--dry-run]" +readonly NAME="$1" PORT="$2" MODE="${3:-}" +[[ "$NAME" =~ ^[a-z0-9-]+$ ]] || die "bad name: $NAME" +[[ "$PORT" =~ ^[0-9]{4,5}$ ]] || die "bad port: $PORT" +command -v docker >/dev/null || die "docker is required" +[[ -d "$SITES_DIR" ]] || die "no Caddy site directory at $SITES_DIR" + +readonly HOST="${NAME}.kuhy.duckdns.org" +readonly TOKEN_FILE="${TOKEN_FILE:-${HOME}/.config/${NAME}-mcp/token}" + +render() { + echo "# Managed by publish_caddy_site.sh -- do not edit by hand." + echo "${HOST} {" + if [[ -f "$TOKEN_FILE" ]]; then + printf '\t@unauth not header Authorization "Bearer %s"\n' "$(<"$TOKEN_FILE")" + printf '\trespond @unauth "unauthorized" 401\n' + fi + printf '\treverse_proxy 127.0.0.1:%s {\n\t\tflush_interval -1\n\t}\n}\n' "$PORT" +} + +# Validate with a throwaway container on a tmp copy: never touches the live edge. +validate_copy() { + local tmp + tmp="$(mktemp -d)" + trap 'rm -rf "$tmp"' RETURN + mkdir -p "$tmp/sites" + cp -a "$SITES_DIR"/. "$tmp/sites/" 2>/dev/null || true + render >"$tmp/sites/${HOST}.caddy" + printf 'import /etc/caddy/sites/*.caddy\n' >"$tmp/Caddyfile" + docker run --rm -v "$tmp/Caddyfile:/etc/caddy/Caddyfile:ro" -v "$tmp/sites:/etc/caddy/sites:ro" \ + "$IMAGE" caddy validate --config /etc/caddy/Caddyfile +} + +validate_copy +[[ "$MODE" == "--dry-run" ]] && { + echo "dry run ok: ${HOST} -> 127.0.0.1:${PORT}" + exit 0 +} + +render >"${SITES_DIR}/${HOST}.caddy" +chmod 600 "${SITES_DIR}/${HOST}.caddy" +docker exec "$CONTAINER" caddy reload --config /etc/caddy/Caddyfile +echo "published https://${HOST} -> 127.0.0.1:${PORT}"