Every tag, automatically.
Auto-generated feed of every released version. New tags land here automatically. For the curated narrative on what each release means, see the changelog.
Release timeline
Every entry below is a real Git tag from the source repository. When a tag is published, our CI notifies this site and the release is filed here for review. Curated language gets edited where it matters; everything else passes through with the original release notes intact. Links back into the source repository are stripped at render time because the repository itself is private — what you see is the work, not a pointer to the code.
The hourly poll is a fallback in case a webhook is missed. A tag normally lands within minutes of being cut; if the webhook drops, the next poll picks it up. The three things you might want elsewhere: the changelog bundles releases into the narrative of what shipped and why, the status page shows engineering pulse (commit heatmap, open work, CI runtime), and individual GitHub release URLs are intentionally omitted — the source isn’t public. The iPhone app is in TestFlight, by request, and the Android app is on Google Play. Request iPhone access · Get it on Google Play Email beta@snapshotscoring.com if a build will not install.
Versioning follows semver: MAJOR.MINOR.PATCH. Major bumps mean breaking changes on the wire (mobile app or WordPress plugin), minor bumps add features without breaking older clients, and patch bumps are bug fixes only. The pipeline auto-versions from main; commit messages tagged feat: trigger a minor bump, fix: a patch, BREAKING CHANGE a major. Releases are tested against the production API on CI-managed emulators and simulators via Maestro before they’re cut, and CI gates merging to main with a Django backend test suite, an iOS E2E run, an Android E2E run, and a CodeQL security scan.
v1.9.43
Release v1.9.43 (scheduled)
v1.9.43
Release v1.9.43 (scheduled)
v1.9.42
Release v1.9.42 (scheduled)
v1.9.42
Release v1.9.42 (scheduled)
v1.9.40
Release v1.9.40 (scheduled)
v1.9.40
Release v1.9.40 (scheduled)
v1.9.39
Release v1.9.39
Audit remediation: 7 criticals, 42 highs, plus the 25 major dependency bumps.
Both platforms E2E-validated on this commit (one known open flow: 106
score-with-penalties, under investigation).
v1.9.39
Release v1.9.39
Audit remediation: 7 criticals, 42 highs, plus the 25 major dependency bumps.
Both platforms E2E-validated on this commit (one known open flow: 106
score-with-penalties, under investigation).
v1.9.37
Release v1.9.37 (scheduled)
v1.9.37
Release v1.9.37 (scheduled)
v1.9.36
What’s Changed
- Validate duplicate score creates (GlitchTip #304) by @ViolatedSine in https://github.com/the source repo/pull/273
v1.9.36
What’s Changed
- Validate duplicate score creates (GlitchTip #304) by @ViolatedSine in https://github.com/the source repo/pull/273
v1.9.35
Release v1.9.35 (scheduled)
v1.9.34
test(e2e): stop asserting an iOS pop that never happens (stage 23)
Stage 23 was the last red flow: 26 passed / 1 failed, with the persistence gate
green, across three consecutive runs.
- back does not pop on iOS — established earlier by the command’s own
duration (80ms on iOS vs 1.4-2.1s on Android for the real animation). The
header-back-button idiom that fixed stage 22 does not work from here either:
MatchDetailScreen retitles itself at runtime to the match name, and when that
title is too wide for the bar UIKit drops the back LABEL entirely, leaving a
bare chevron with no matchable text. Confirmed against the live run — the
selector interpolated correctly to “E2E Match 0829161824|Match|Back” and
matched nothing, so it was not a variable problem.
On iOS the flow now returns to the list by tapping the Matches tab, which is
reachable from any stack depth — exactly what stages 15 and 24 already do and
pass. The per-hop assertions stay on ANDROID, where the race they guard against
(a second back landing on the tab root) is real.
It deliberately does NOT assert matchDetailScreen on iOS. Asserting a pop the
platform never performs would be a test asserting fiction — which is the same
failure that let three bugs ship today behind passing tests. Stage 09 covers
match detail on both platforms.
Co-Authored-By: Claude Opus 5 (1M context)
Claude-Session: https://claude.ai/code/session_01NHxqBcVXDSGh6V7nwa9QTU
v1.9.34
test(e2e): stop asserting an iOS pop that never happens (stage 23)
Stage 23 was the last red flow: 26 passed / 1 failed, with the persistence gate
green, across three consecutive runs.
- back does not pop on iOS — established earlier by the command’s own
duration (80ms on iOS vs 1.4-2.1s on Android for the real animation). The
header-back-button idiom that fixed stage 22 does not work from here either:
MatchDetailScreen retitles itself at runtime to the match name, and when that
title is too wide for the bar UIKit drops the back LABEL entirely, leaving a
bare chevron with no matchable text. Confirmed against the live run — the
selector interpolated correctly to “E2E Match 0829161824|Match|Back” and
matched nothing, so it was not a variable problem.
On iOS the flow now returns to the list by tapping the Matches tab, which is
reachable from any stack depth — exactly what stages 15 and 24 already do and
pass. The per-hop assertions stay on ANDROID, where the race they guard against
(a second back landing on the tab root) is real.
It deliberately does NOT assert matchDetailScreen on iOS. Asserting a pop the
platform never performs would be a test asserting fiction — which is the same
failure that let three bugs ship today behind passing tests. Stage 09 covers
match detail on both platforms.
Co-Authored-By: Claude Opus 5 (1M context)
Claude-Session: https://claude.ai/code/session_01NHxqBcVXDSGh6V7nwa9QTU
v1.9.33
fix(results-plugin): restore the PHPStan gate’s teeth on snapshot_monitored (#263)
7b3dcaf3 showed the gate did not catch a fresh #295 in snapshot-admin-tools
because 35e7f41f had left monitored($callback) untyped. The same hole exists in
snapshot-results, and #262 is what opened it: to silence PHPStan calling the new
is_callable() guard unreachable, that PR weakened @param callable to @param
mixed. Measured on this tree — a dead mapping at init was reported CLEAN.
So #262 fixed the runtime fatal and disabled the static check that had just
found the trap. Both halves are needed and they pull against each other.
Resolved the way 7b3dcaf3 resolved it for the staff plugin: keep @param callable
so PHPStan can check the [Class, ‘method’] arrays, keep the is_callable() guard
as the runtime seatbelt, and ignore the resulting “unreachable ternary” in
phpstan.neon with the reasoning recorded. Deliberately PHPDoc, not a native
hint — a native hint throws the very TypeError the wrapper prevents.
Verified against this baseline: clean tree reports 0; a dead mapping at init
reports 1, naming Snapshot_Shortcodes::method_that_does_not_exist at the call
site. php -l clean on 8.0 and 8.3.
Baseline 321 -> 322: the Snapshot_REST::monitored(string $method) dynamic
callable is a genuine finding again now that the annotation is back. It marks
the remaining latent trap and should stay visible.
Claude-Session: https://claude.ai/code/session_01PKB8Yct2jDWE2b7YdRMHuG
Co-authored-by: Travis Nyhoff Co-authored-by: Claude Opus 5 (1M context)
v1.9.33
fix(results-plugin): restore the PHPStan gate’s teeth on snapshot_monitored (#263)
7b3dcaf3 showed the gate did not catch a fresh #295 in snapshot-admin-tools
because 35e7f41f had left monitored($callback) untyped. The same hole exists in
snapshot-results, and #262 is what opened it: to silence PHPStan calling the new
is_callable() guard unreachable, that PR weakened @param callable to @param
mixed. Measured on this tree — a dead mapping at init was reported CLEAN.
So #262 fixed the runtime fatal and disabled the static check that had just
found the trap. Both halves are needed and they pull against each other.
Resolved the way 7b3dcaf3 resolved it for the staff plugin: keep @param callable
so PHPStan can check the [Class, ‘method’] arrays, keep the is_callable() guard
as the runtime seatbelt, and ignore the resulting “unreachable ternary” in
phpstan.neon with the reasoning recorded. Deliberately PHPDoc, not a native
hint — a native hint throws the very TypeError the wrapper prevents.
Verified against this baseline: clean tree reports 0; a dead mapping at init
reports 1, naming Snapshot_Shortcodes::method_that_does_not_exist at the call
site. php -l clean on 8.0 and 8.3.
Baseline 321 -> 322: the Snapshot_REST::monitored(string $method) dynamic
callable is a genuine finding again now that the annotation is back. It marks
the remaining latent trap and should stay visible.
Claude-Session: https://claude.ai/code/session_01PKB8Yct2jDWE2b7YdRMHuG
Co-authored-by: Travis Nyhoff Co-authored-by: Claude Opus 5 (1M context)
v1.9.32
feat(ops): provision_circuit_host –ends-on never — founding members are free for life
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NHxqBcVXDSGh6V7nwa9QTU
v1.9.32
feat(ops): provision_circuit_host –ends-on never — founding members are free for life
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NHxqBcVXDSGh6V7nwa9QTU
v1.9.31
feat(mobile): peer Wi-Fi sync, Stage 1 — host a match sync session on the range Wi-Fi, join by QR, push/pull score bundles with no internet
One phone hosts (react-native-tcp-socket server on a random port, 80-bit session token from
crypto.getRandomValues), others join by scanning the host’s QR (ip:port:token, manual code as
fallback), push their scores and receive the host’s merged bundle in the same round trip.
Merge never overwrites locked scores; new scores enter the local store with temp ids so the
existing offline queue reconciles them with the server later. Dependency-free HTTP/1.1 subset
with a real TCP loopback test in Jest. Removes the non-functional LocalSyncContext /
localNetworkSync stub. iOS: NSLocalNetworkUsageDescription (pod install needed); Android: raw
TCP client, no cleartext exemption. Full mobile suite 6809 passed.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NHxqBcVXDSGh6V7nwa9QTU
The story behind each release.
This page lists every tag. The changelog groups them into the work that mattered — what landed, why, and what’s next. The status page has the live engineering pulse — commit heatmap, open work, CI runtime.