Skip to content

244 - Awaitable Watch-Mode Screenshots#245

Open
bracyw wants to merge 7 commits into
developfrom
244-task--awaitable-watch-mode-captures
Open

244 - Awaitable Watch-Mode Screenshots#245
bracyw wants to merge 7 commits into
developfrom
244-task--awaitable-watch-mode-captures

Conversation

@bracyw

@bracyw bracyw commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Changes

Makes the headless screenshot harness (#242) scriptable for agent-driven development. Watch-mode captures now emit a completion signal — a NERO_SHOT_DONE ok|fail "<path>" line in the app's log plus a <trigger>.done sentinel — on both success and failure, so a caller can await a capture instead of racing the output-file write. Also fixes multi-word page labels in watch mode, adds a tunable settle delay, and ships a thin /nero-shot skill that bundles a nero-shot.sh wrapper for driving the trigger file.

Notes

  • .claude/ is gitignored repo-wide, so .gitignore is adjusted to un-ignore only .claude/skills/nero-shot/; every other local skill stays untracked.
  • No runtime impact — everything stays opt-in behind the existing NERO_SCREENSHOT* env vars; production paths are unchanged.
  • The trigger is now split on the last whitespace, treating the tail as the output path only when it looks like one (a slash, backslash, or file extension), which keeps multi-word labels intact.
  • Cross-platform: the skill auto-discovers the binary instead of assuming the macOS kit (platforms.md documents the per-OS paths), the wrapper resolves a relative output path against the caller's cwd and waits on a non-empty sentinel, a bad settle-delay value warns rather than silently defaulting, and a blank or uniform grab (a headless render with no GPU) fails loudly with a QT_QUICK_BACKEND=software hint instead of saving an empty PNG.

Test Cases

Dogfooded with the skill's nero-shot.sh wrapper against a running offscreen watch-mode build:

  • HOME → saved (single-word label)
  • PIT - DRIVE → saved (multi-word label; previously failed as unknown page "PIT")
  • OFF with no OUT arg → saved to the default <trigger dir>/off.png
  • BOGUS (unknown page) → fails fast (rc=1) with a fail sentinel, no hang
  • Launched with NERO_SCREENSHOT_DELAY_MS=300 to exercise the tunable delay
  • Verified the NERO_SHOT_DONE line and <trigger>.done contents for each

Review fixes re-dogfooded: multi-word labels still capture; non-.png and backslash output paths now split off correctly (previously unknown-page); a relative wrapper OUT resolves to an absolute path; a bogus delay value warns instead of silently defaulting to 500; and the blank-grab guard was verified not to misfire on real screens.

Checklist

  • All commits are tagged with the ticket number
  • No linting errors / newline at end of file warnings
  • All code follows repository-configured formatting (clang-format)
  • No merge conflicts
  • All checks passing
  • Remove any non-applicable sections of this template
  • Assign the PR to yourself
  • Request reviewers & ping on Slack
  • PR is linked to the ticket (fill in the closes line below)

Closes #244

- Emit a NERO_SHOT_DONE completion line and a <trigger>.done sentinel on
  both success and failure so callers can await a capture instead of
  racing the output-file write
- Parse the watch trigger on the last whitespace (path-like tail only) so
  multi-word labels such as "PIT - DRIVE" capture correctly
- Add NERO_SCREENSHOT_DELAY_MS to override the fixed 500ms settle delay
- Add scripts/nero-shot.sh (write trigger -> await sentinel -> print path)
  and a thin .claude/skills/nero-shot skill that wraps it; document in readme
- Un-ignore only .claude/skills/nero-shot so the shared skill is tracked
@bracyw bracyw self-assigned this Jul 7, 2026
bracyw added 4 commits July 6, 2026 21:03
Co-locate the wrapper with the skill that uses it so /nero-shot is self-contained; update the path references in SKILL.md and the readme.
Build the status payload once and reuse it for the log line and the sentinel; write the sentinel with QFile::write instead of a QTextStream and drop the redundant close(). Output is byte-identical. Correct the comment: qInfo goes to stderr, not stdout.
@bracyw
bracyw marked this pull request as ready for review July 7, 2026 16:28
@bracyw
bracyw requested a review from TheJeffreyKuo July 7, 2026 16:28

@TheJeffreyKuo TheJeffreyKuo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few changes. Also worth a pass for Linux and Windows compatibility in the skill doc.


# Fresh handshake: drop any stale sentinel, then fire the request.
rm -f "$done_file"
printf '%s %s\n' "$page" "$out" >"$trigger"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A relative OUT saves relative to the app's cwd, so the printed path doesn't resolve from the caller's directory. Absolutizing it here would also sidestep the path sniffing on the app side.

const int lastWs = request.lastIndexOf(QRegularExpression("\\s"));
if (lastWs >= 0) {
const QString tail = request.mid(lastWs + 1);
if (tail.contains('/') || tail.endsWith(".png", Qt::CaseInsensitive)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An out arg with no slash that isn't .png (shot.jpg, or any backslash Windows path) gets folded into the page label and fails as unknown page.

Comment thread readme.md Outdated
echo "PIT - DRIVE /tmp/pit.png" > /tmp/nero-shot

# Each capture reports completion: a `NERO_SHOT_DONE ok "<path>"` line on
# stdout, plus a `<trigger>.done` sentinel file for callers not reading stdout

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qInfo goes to stderr, not stdout.

Comment thread .claude/skills/nero-shot/SKILL.md Outdated
BIN="$NERO/NERODevelopment/build/macos_development-Debug/NEROApp" # build dir matches your Qt Creator kit
```

If you changed code, rebuild first: `/build-run build`. No broker is needed —

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/build-run doesn't appear to be tracked anywhere, other checkouts won't have it.

Comment thread .claude/skills/nero-shot/SKILL.md Outdated

```bash
NERO=$(git rev-parse --show-toplevel) # run from anywhere inside the repo
BIN="$NERO/NERODevelopment/build/macos_development-Debug/NEROApp" # build dir matches your Qt Creator kit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default is specific to the macOS kit, won't resolve on Linux checkouts.

grabAndSave(out, quitAfter);
});
// Settle delay is tunable so bulk captures aren't stuck at 500ms
const int delayMs = qEnvironmentVariableIntValue("NERO_SCREENSHOT_DELAY_MS");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A non numeric value silently falls back to 500 here, the ok overload would let it warn like setupWatch does.

# Poll for the sentinel NEROApp writes once the grab (or failure) completes.
tries=$((timeout * 10))
i=0
while [ ! -f "$done_file" ]; do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The done file exists empty for an instant before the payload lands, -s instead of -f avoids catching it.

bracyw added 2 commits July 8, 2026 12:08
…race

- nero-shot.sh: absolutize a relative OUT against the caller's cwd, and wait for a non-empty sentinel (-s) so the poll can't catch the .done file in the empty instant between create and write
- screenshottool.cpp: treat the trailing token as an output path when it has a slash, backslash, or file extension (not just .png); warn on a set-but-bogus NERO_SCREENSHOT_DELAY_MS instead of silently using 500
- nero-shot skill: auto-discover the binary instead of hardcoding the macOS kit path, drop the untracked /build-run reference, add platforms.md with per-OS paths
- readme: qInfo goes to stderr not stdout; note the broadened path rule
- grabAndSave grabs into a QImage and fails (warns + fail sentinel) on a null or entirely-uniform image instead of saving a blank PNG, so a headless render failure is loud rather than a silent success
- platforms.md: QT_QUICK_BACKEND=software remedy for headless Linux, and sharper Windows guidance (WSL is the clean path; native needs explicit trigger/output paths)
- SKILL.md: note the blank-capture failure and the software-backend fallback
@bracyw
bracyw requested a review from TheJeffreyKuo July 8, 2026 17:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Screenshot Tool - Awaitable Watch-Mode Captures

2 participants