Skip to content

fix: harden combined text review and MCP privacy - #154

Merged
johnny4young merged 3 commits into
mainfrom
fix/combined-text-review-followups
Sep 20, 2026
Merged

johnny4young merged 3 commits into
mainfrom
fix/combined-text-review-followups

Conversation

@johnny4young

@johnny4young johnny4young commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Purpose

This follow-up is worth retaining: it computes the combined string once per render, resets per-attempt feedback, contains the preview accessibility identity, and waits for all three synthetic seed rows before recording the UI test's baseline. The paste-sink argument now uses its documented copy-only spelling.

The missing-store branch now reports failure defensively. This is not evidence of a reachable enabled-but-inert button: AppModel.fullStore is immutable and initial loading already disables Copy when it is absent.

Review remediation

  • Confirmed privacy defect, fixed: intrinsically masked kinds (jwt, creditCard, secret) could leave MCP in clear when isSensitive was false. All five tool boundaries now use ClipSafePresentation.requiresMasking. Search also excludes masked kinds before SQL LIMIT, preserving useful safe results. Context-miss denials still precede the privacy veto; scope and write authorization are unchanged.
  • Regression coverage: both embedded and live curated grants cover all three masked kinds across search, direct read, pin and stack. All search modes cover a masked newer match with limit 1 and an older permitted match.
  • Native UI: assert a unique preview accessibility identifier; cover large panel text with a custom separator, visible Copy/Cancel, successful copying, and unchanged clip count. Use the existing controlled panel-position fixture so synthetic screenshot evidence works on multi-display hosts.
  • Main synchronization: merged main at 358dd1c1939091614e2f3cf22ecd22582c2039b4 without conflicts. Screen OCR changes are preserved.

Validation at 3437614bf344713db12ef81501380a347997189e

  • Local make format, make lint: passed, 499 Swift files, zero violations.
  • Local make test: 1,222 tests across 186 suites passed (all eight test products, not only the last product's 95 tests).
  • MCP regression red/green: six masked-kind cases initially produced 60 failed assertions; now green, plus six search-mode/grant cases.
  • Local GanchoUITests/CombinedTextUITests: 2 passed, 0 failed, 0 skipped; screenshots inspected. The first attempt failed at screenshot creation on a secondary display; controlled placement fixed the harness without removing screenshots or assertions.
  • CI: passed on this exact SHA — lint, 1,222 tests, 82.5% production-source coverage, StoreKit tests and both app builds.
  • Full macOS/iOS UI: passed on this exact SHA — macOS 71 passed / 3 skipped / 0 failures; iOS 13 passed / 0 skipped / 0 failures. The macOS skips are the two Development-signing-dependent external-helper cases and the hosted status-menu Settings window case (deep-link Settings remains covered). GitGuardian passed; the non-blocking beta canary was intentionally skipped by workflow policy.

Findings triaged without speculative changes

  • The alleged clipped actions were not reproduced at the largest supported panel text size (.xLarge) with the extra custom-separator field. Both actions are visible, hittable and exercised. This does not certify every arbitrary accessibility size/localization combination.
  • Multiple independent sheet bindings are a pre-existing architectural concern, not a demonstrated regression from this two-file follow-up. No navigation rewrite was made without a failing scenario.
  • The absence of the Safari AutoComplete window is asserted by the native flow; this does not establish that autocorrectionDisabled() controls every AppKit AutoFill configuration.

No merge, release, installation, signing-policy change, or gate weakening was performed.

`copy()` inherited the previous attempt's outcome. Once a clip turned
protected mid-review, the red "remove unavailable or incompatible clips"
line stayed up after the user removed it, and the orange "review and
copy again" line never cleared. Both reset at the start of every
attempt now, the way ManualOCRReview.run() already does. The same
function also returned silently when the durable store was missing,
leaving an enabled Copy button that did nothing and said nothing; it
reports the failure instead.

`composed` joins every part (a documented 1 MiB ceiling) and body read
it three times — for the preview, for the error condition and for the
Copy button's disabled state — so typing a custom separator re-joined
the whole document three times per keystroke. It is hoisted into one
value per render.

The preview ScrollView carried a bare `.accessibilityIdentifier`, which
propagates onto every descendant and overwrites the preview text's own
element identity, so a query for `combined-text-preview` was ambiguous
between the container and the text inside it. Contained first.

In the UI test, the row count was snapshotted once the *first* seeded
row existed, racing the fire-and-forget task that inserts three, so the
later comparisons could run against a count of 1 or 2. It waits for the
last seeded row. The `-ui-test-paste-sink` argument also passed the
invented `copiedOnly` instead of the documented `copy-only`; the two
parse the same today, which is exactly what makes the drift invisible.

Verified: make lint clean, make build succeeds, and
GanchoUITests/CombinedTextUITests passes locally.
@johnny4young johnny4young changed the title fix(mac): clear stale banners and join the combined text once per render fix: harden combined text review and MCP privacy Sep 20, 2026
@johnny4young
johnny4young merged commit 31948b0 into main Sep 20, 2026
6 checks passed
@johnny4young
johnny4young deleted the fix/combined-text-review-followups branch September 20, 2026 14:07
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.

1 participant