diff --git a/docs/dev/driving-the-app.md b/docs/dev/driving-the-app.md index 19bedf5..8f55916 100644 --- a/docs/dev/driving-the-app.md +++ b/docs/dev/driving-the-app.md @@ -190,6 +190,26 @@ Other gotchas: - Clicks are in **logical points** (Retina-safe): `CGWindowBounds` and `cliclick` agree, so a fraction of the window maps correctly on any display scale. +## Attaching a screenshot to a PR + +`gh` **cannot** upload an image into GitHub markdown — that path is the web UI's private upload +endpoint, so a comment/PR body built from the CLI can only *reference* an image by URL. GitHub +renders `![](url)` for any URL that returns image bytes, and this is a **public** repo, so it +serves its own files at `raw.githubusercontent.com`. [`scripts/pr-image.sh`](../../scripts/pr-image.sh) +parks the PNG on a dedicated orphan **`assets`** branch (never merged, never in a PR diff, never in +your working tree — it is built with git plumbing) and prints paste-ready markdown: + +```bash +scripts/pr-image.sh --prefix 198 .context/shots/BEFORE-you-are-sending.png \ + .context/shots/AFTER-dapp-requests.png +# → ![198-BEFORE…](https://raw.githubusercontent.com///assets/198-BEFORE….png) +# ![198-AFTER…](https://raw.githubusercontent.com///assets/198-AFTER….png) +``` + +It **appends** to the `assets` branch (old images survive) and replaces any same-named file. It +refuses to run against a private repo, whose raw URLs won't render for viewers — drag-drop into the +web UI instead there. + ## Linux / CI For a headless Linux or Claude-on-the-web session, the mechanics are different (virtual X display diff --git a/scripts/pr-image.sh b/scripts/pr-image.sh new file mode 100755 index 0000000..c0a0096 --- /dev/null +++ b/scripts/pr-image.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# pr-image.sh — publish local image(s) to the repo's `assets` branch and print +# paste-ready GitHub markdown that renders inline in an issue / PR / comment. +# +# WHY THIS EXISTS +# `gh` cannot upload an image into GitHub markdown — that path is the web UI's +# private upload endpoint. But GitHub renders `![](url)` for ANY URL that returns +# image bytes, and a PUBLIC repo serves its own files at raw.githubusercontent.com. +# So we park screenshots on a dedicated orphan `assets` branch (never merged, never +# in a PR diff, never in the working tree) and reference their raw URLs. This is the +# standard CI trick and it is fully scriptable. +# +# HOW IT WORKS (pure plumbing — does NOT touch your working tree, index, or HEAD) +# hash-object -w → merge into the existing assets tree → commit-tree → push. +# +# USAGE +# scripts/pr-image.sh [ ...] +# scripts/pr-image.sh --prefix 198 before.png after.png # names → 198-before.png … +# +# Then paste the printed markdown into `gh pr edit --body-file` / `gh pr comment`. +# +# REQUIREMENTS +# - a PUBLIC repo (raw URLs need no auth). A private repo's raw URLs won't render +# for viewers — this script refuses to run against one. +# - an `origin` remote on GitHub. +set -euo pipefail + +BRANCH="assets" +PREFIX="" +if [[ "${1:-}" == "--prefix" ]]; then + PREFIX="${2:?--prefix needs a value}-" + shift 2 +fi +[[ $# -ge 1 ]] || { echo "usage: $0 [--prefix

] [ ...]" >&2; exit 2; } + +# Refuse a private repo: viewers can't render its raw URLs without a token. +vis=$(gh repo view --json visibility --jq .visibility 2>/dev/null || echo UNKNOWN) +if [[ "$vis" != "PUBLIC" ]]; then + echo "error: repo visibility is '$vis' — raw URLs only render for a PUBLIC repo." >&2 + echo " For a private repo, drag-drop the image into the PR web UI instead." >&2 + exit 1 +fi + +nwo=$(gh repo view --json nameWithOwner --jq .nameWithOwner) + +# Start the new tree from the existing assets branch (so prior images survive), or +# empty if the branch doesn't exist yet. +git fetch -q origin "$BRANCH" 2>/dev/null || true +declare -a tree_lines=() +parent_args=() +if git rev-parse -q --verify FETCH_HEAD >/dev/null 2>&1 && \ + git ls-tree "origin/$BRANCH" >/dev/null 2>&1; then + while IFS= read -r line; do tree_lines+=("$line"); done < <(git ls-tree "origin/$BRANCH") + parent_args=(-p "origin/$BRANCH") +fi + +# Add each image as a blob, replacing any existing entry with the same name. +declare -a names=() urls=() +for img in "$@"; do + [[ -f "$img" ]] || { echo "error: no such file: $img" >&2; exit 1; } + name="${PREFIX}$(basename "$img")" + blob=$(git hash-object -w "$img") + # drop any prior entry with this name, then append the new one + filtered=() + for l in "${tree_lines[@]:-}"; do + [[ -n "$l" && "$l" == *$'\t'"$name" ]] && continue + [[ -n "$l" ]] && filtered+=("$l") + done + tree_lines=("${filtered[@]:-}") + tree_lines+=("$(printf '100644 blob %s\t%s' "$blob" "$name")") + names+=("$name") + urls+=("https://raw.githubusercontent.com/$nwo/$BRANCH/$name") +done + +tree=$(printf '%s\n' "${tree_lines[@]}" | git mktree) +commit=$(git commit-tree "$tree" "${parent_args[@]}" -m "assets: $*") +git push -q origin "$commit:refs/heads/$BRANCH" + +echo "published to the '$BRANCH' branch. Paste-ready markdown:" >&2 +echo >&2 +for i in "${!urls[@]}"; do + printf '![%s](%s)\n' "${names[$i]%.*}" "${urls[$i]}" +done