feat(screen-hash): stable content hash on snapshot and render-wait results#127
Merged
Conversation
Capture the screenHash design from the grill-with-docs pass: a stable digest of normalized visible screen text, computed from the same canonical visible text as the Screen Stability check and text Render Wait matching (unified, no behavior change), and distinct from the screenshot pixel sha256. Change-Id: I73d19ebe921f316bff9dab166c8ba756f0cdd3fe Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Thomas Kosiewski <tk@coder.com>
Product requirements for an optional screenHash field on snapshot and render-wait results: a stable content change-token computed from the canonical visible text and unified with the Screen Stability compare, with no behavior change. Produced via the to-prd flow. Change-Id: Icbd22c29b3785476ee13ed62c2c100cdc45d7c22 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Thomas Kosiewski <tk@coder.com>
Add an optional `screenHash` field — a lowercase 64-char SHA-256 of the canonical visible-screen text (`visibleLines[].text` joined by `\n`) — to snapshot results (structured and text) and to render-wait results that observed a Semantic Snapshot. It gives a caller a stable content-change token that is unaffected by cursor motion or no-op repaints. - Extract one shared canonical-visible-text helper (src/renderer/canonicalScreen.ts) and route the Screen Hash, the host Screen Stability compare, and the text Render Wait matcher through it; add a UTF-8-pinned sha256Hex util and consolidate the duplicate Sha256HexSchema into one exported definition. - Hash any observed snapshot: matched live waits, snapshot captures, and the offline host-unreachable matched:false fallback carry it; live timeouts, consecutive-failure giveups, and replay errors omit it. - Carry the hash on matched batch wait-step records. - Converge both renderer backends on one canonical screen form so the hash is renderer-independent: ghostty-web now decodes full grapheme clusters, keeps interior blank cells as spaces, and right-trims ASCII spaces only; libghostty-vt pads visible lines to exactly `rows`. This intentionally aligns the default ghostty-web stability/text-wait comparand on grapheme / interior-gap / non-ASCII-trailing screens, pinned by characterization and cross-backend tests. Closes #125. Change-Id: I698af5dbf8f66c70f49661712652b24d70415f0a Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Thomas Kosiewski <tk@coder.com>
Integration and e2e tests drive real PTY hosts and headless-browser renderers, which can transiently fail under machine load (e.g. a screenshot render or host RPC hiccup) even when the code is correct. Pass `--retry=2` to the `test:integration`, `test:e2e`, and combined `test` scripts so a flaky attempt is retried in place instead of failing the sharded CI job; a genuine failure still fails all three attempts. `test:unit` stays strict so the unit gate keeps catching real unit flakes. Document the policy in CONTRIBUTING and CI. Change-Id: I7696b26b9a5b97102b4543a6bcf485ce30ea4be4 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Thomas Kosiewski <tk@coder.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional
screenHashto snapshot and render-wait results — a lowercase 64-char SHA-256 of the canonical visible-screen text — giving a caller (typically an AI coding agent) a stable token to tell whether the rendered screen content actually changed between two observations, without diffing full text. Unlike the event-log sequence it does not advance on cursor moves or no-op repaints; unlike the screenshot pixelsha256it is the semantic (text) counterpart.Implements the screen-hash PRD (
docs/prd/screen-hash/PRD.md). Closes #125.What's included
screenHashfieldscreenHashon the structured + text snapshot results and on render-wait results, validated by the existing (now consolidated, single-source)Sha256HexSchema.visibleLines[].textjoined by\n— visible screen only (no scrollback, cursor, or styles).src/renderer/canonicalScreen.ts) feeds the Screen Hash, the host Screen Stability compare, and the text Render Wait matcher, so the three can never disagree about "the screen".Key decisions (settled during planning)
matched:falsefallback all carry it; only results that observed no snapshot (live timeout, consecutive-failure giveup, replay-error throw) omit it — so "absent hash ⇔ no screen observed" holds literally.rowslines; full grapheme clusters; interior blank cells as spaces; ASCII-only trailing trim) so the hash is renderer-independent. This intentionally changes the defaultghostty-webstability/text-wait comparand on grapheme / interior-gap / non-ASCII-trailing screens — a deliberate, narrow change pinned by characterization + cross-backend tests (see PRD §Out of Scope and user-story feat: Week 7 — contract ratification, test locks, proof bundles, and docs sync #10).CI / test hardening (separate commit
ci: …, independent of the feature)test:integration,test:e2e, and the combinedtestnow retry transient failures (--retry=2);test:unitstays strict. Browser/host/PTY e2e tests can flake under machine load (a screenshot-render/RPC hiccup that passes on retry and in isolation) — this keeps such flakes from spuriously failing CI while a genuine failure still fails all three attempts. Documented in CONTRIBUTING + CI.Verification
@coder/libghostty-vt-nodeaddon) and run in CI; the convergence's pure decode logic is unit-tested via exported twins (assembleCanonicalLine/stripTrailingAsciiSpaces).Notes for reviewers
screenHashlives on the result, not onSemanticSnapshot(the snapshot shape is unchanged).cells[](blank ='') vsvisibleLines[].text(blank =' ', spacer dropped) asymmetry is documented inline inghosttyWeb/backend.ts; the hash sourcesvisibleLines[].textonly.ScreenshotResultchange — screenshots keep only their pixelsha256.🤖 Generated with Claude Code