diff --git a/.claude/hooks/pre-push-compose-escaping.sh b/.claude/hooks/pre-push-compose-escaping.sh new file mode 100755 index 0000000000..0cdb74e34d --- /dev/null +++ b/.claude/hooks/pre-push-compose-escaping.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# PreToolUse gate: the Compose resource catalog must not carry Android escaping. +# +# Fires on `git push` (Bash tool) and on the create_pull_request MCP tool. +# Delegates to compose_escaping_check.py, which scans +# `*/src/*/composeResources/values*/strings.xml` for \' \" \? \@ and `tools:` +# attributes. Compose resolves only \uXXXX, \n and \t, so anything else carried +# verbatim out of an Android res tree renders literally -- the login screen once +# read `Don\'t have a Nostr account?`. +# +# Why a dedicated hook: this is reintroduced by every Crowdin sync, because +# Crowdin holds the Android-escaped source. It came back twice in two days, 2,068 +# escaped apostrophes across 40 locales each time. Nothing in the Gradle build +# fails on it -- the strings simply ship wrong -- so there is no slow gate this +# stands in for; it is the only gate. +# +# Run the scan by hand any time with: .claude/hooks/compose_escaping_check.py +set -uo pipefail + +hook_dir="$(cd "$(dirname "$0")" && pwd)" +cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0 + +payload="$(cat)" + +# Same cheap pre-filter as the orphan-strings gate: only a payload mentioning a +# push or the PR tool can possibly match, and this runs on every Bash call. +case "$payload" in + *push*|*pull_request*) ;; + *) exit 0 ;; +esac + +printf '%s' "$payload" | python3 "$hook_dir/lib/git_push_gate.py" || exit 0 + +exec python3 "$hook_dir/compose_escaping_check.py" diff --git a/.claude/settings.json b/.claude/settings.json index 929a186506..c0238b52b5 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -13,6 +13,11 @@ "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-push-orphan-strings.sh", "timeout": 30 + }, + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-push-compose-escaping.sh", + "timeout": 30 } ] } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c55e0819b..a6a31dd3fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,9 @@ jobs: - name: Orphaned translations (no locale string may outlive its default key) run: .claude/hooks/orphan_strings_check.py + - name: Compose catalog escaping (Android escapes render literally there) + run: .claude/hooks/compose_escaping_check.py + - name: Set up JDK 21 uses: actions/setup-java@v6.0.0 with: