Skip to content

REQ-VISUALIZATION-005: prove M1/M2 viewport readability with real-browser layout - #421

Merged
zendev-acceptor[bot] merged 3 commits into
masterfrom
claude/issue-411-viewport-readability
Sep 11, 2026
Merged

zendev-acceptor[bot] merged 3 commits into
masterfrom
claude/issue-411-viewport-readability

Conversation

@zendev-author

@zendev-author zendev-author Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Closes #411

Achieved outcome

The M1/M2 Pages desktop (1280px) and narrow (360px) viewport smoke tests now assert real, layout-aware visibility (Playwright/Chromium locator.isVisible() — non-empty bounding box and non-visibility:hidden computed style) instead of getComputedStyle(...).display !== "none" in jsdom, which has no box-model layout. Three negative-control tests prove a visibility: hidden regression, a zero-height clipping regression (overflow: hidden; max-height: 0), and an opacity: 0 regression on the populated preview body are each caught, while DOM population and the JSON fetch still succeed — exactly the blind spot Issue #411 named and demonstrated with a concrete CSS reproduction.

Rework (round 1): the ACCEPTOR's CHANGES_REQUESTED verdict on this PR's first head (e0bab30), independently confirming a SLOPSTER QA: FINDING comment, showed that Playwright's isVisible() does not check opacity (https://playwright.dev/docs/actionability#visible): an opacity: 0 regression on a populated preview body would pass every positive assertion this PR originally added, even though the content is fully transparent and unreadable. This head adds an isReadable() helper (isVisible() plus an explicit computed opacity !== "0" check), uses it for the two positive viewport tests' body assertions, and adds a third negative-control test proving an opacity: 0 regression on #m1-preview-body is caught.

Tested revision

f57a366 (implementation at a291ebc + f57a366 for the opacity-readability fix; e0bab30 in between only added the ledger row, superseded by this head's ledger update)

Changed artifacts

  • package.json — added playwright devDependency; added a pretest script (playwright install chromium) so npm test self-installs the browser binary, matching the existing npm ci && npm run typecheck && npm test && npm run build verification sequence with no workflow file changes.
  • package-lock.json — lockfile update for the new dependency.
  • src/diagnostics/m1m2-pages-render.test.ts — launches a real Chromium browser in beforeAll/closes it in afterAll (reusing the existing local static file server); replaces the two viewport tests with real-browser navigation + isVisible()/isReadable() checks; adds renderPageInBrowser() helper supporting CSS injection; adds three negative-control tests (visibility: hidden on #m1-preview-body, clipping on #m2-preview-body, opacity: 0 on #m1-preview-body); adds isReadable() helper closing the opacity blind spot the ACCEPTOR identified.
  • docs/spec/implementation_status.csv / docs/spec/IMPLEMENTATION_STATUS.md — updated the existing REQ-VISUALIZATION-005 row (see below).

Acceptance criteria

Issue #411's criteria:

  • 1. The focused smoke exercises a layout-capable rendering path at 1280px and 360px and verifies the required M1/M2 content is visible/readable, not merely present in textContent — real Chromium via Playwright, isReadable() (isVisible() + non-zero computed opacity) on both panel and body elements.
  • 2. A controlled visibility: hidden or equivalent clipping/unreadability regression on the populated preview body makes the focused smoke fail while JSON fetching and DOM population still succeed — three dedicated negative-control tests (hidden, clipped, opacity-0), covering the mechanisms identified across this PR's review.
  • 3. PR REQ-VISUALIZATION-005: execute the real M1/M2 DOM render path in the Pages smoke test #407's DOM-population negative controls remain covered and passing after the viewport proof is strengthened — untouched, still passing.
  • 4. Existing TypeScript and retained .NET gates remain passed — see Checks.

Checks

Check Outcome Evidence
npm ci passed clean install, 85 packages
npm run typecheck passed tsc --noEmit clean
npm test passed 40 files, 563 tests passed (560 pre-existing + 3 net new: 2 viewport tests rewritten in place, 3 negative-control tests added)
npm run build passed vite build succeeded
dotnet restore passed both projects restored
dotnet build --configuration Release --no-restore passed 0 warnings, 0 errors
dotnet test --configuration Release --no-build passed 45/45 (REQ-MIGRATION-003 maintained)
python scripts/implementation_status.py --check passed see ledger update below
python scripts/status_lint.py --repo drevendev/trade_simulation --self 421 passed 29 ledger rows agree with merged PRs

Not checked

Firefox/WebKit rendering paths are not exercised — only Chromium. The repository's Pages target is a static site with no browser-specific CSS in docs/index.html, so this is a low residual risk, but it is not proven here.

Assumptions and unknowns

  • Assumption: adding a pretest npm script (rather than a workflow-file change) is the correct way to make CI self-install the Chromium binary, since .github/workflows/** is out of scope for this run per AGENTS.md's authority table, and the existing verification sequence already runs npm test after npm ci. This was already evidenced green by this PR's own required checks on the prior head.
  • Ledger provenance: per the ACCEPTOR's review on the prior head, rewriting REQ-VISUALIZATION-005's structured provenance to ISSUE=411, PR=421 with a blank MERGE_COMMIT while this PR is open is the established, previously-accepted convention in this repo (PR REQ-VISUALIZATION-005: execute the real M1/M2 DOM render path in the Pages smoke test #407 did the same for this row; a follow-up ledger PR fills in the real SHA after merge).

Highest-risk area for review

None outstanding from the implementation itself. The opacity blind spot identified in round 1 is now closed with a dedicated helper and negative control. The remaining reviewer judgment call is whether a pretest-driven browser download is an acceptable pattern for this repository's CI cost/latency going forward, versus caching the browser binary in a later change.

Remaining gate

None known. status:in-progress remains set on Issue #411 pending this round's re-review; ACCEPTOR should independently re-run the full check sequence to confirm the pretest Chromium download succeeds in the actual CI environment and that the opacity fix addresses the prior refusal.

🤖 Generated with Claude Code

github-actions Bot and others added 2 commits September 11, 2026 03:49
…y!=none

The desktop/narrow viewport smoke tests only checked getComputedStyle(...).display
!== "none" and textContent in jsdom, which has no box-model layout: a CSS rule that
hides (visibility: hidden) or clips (overflow: hidden + max-height: 0) the populated
preview body would leave every assertion green. Replace those two tests with real
Chromium navigation via Playwright and assert locator.isVisible(), which reflects an
actual non-empty bounding box and non-hidden computed style. Add negative-control
tests proving a visibility:hidden regression and a zero-height clipping regression on
the populated body are each caught while DOM population/JSON fetch still succeed.

Closes #411

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copy link
Copy Markdown
Owner

QA on current head e0bab302e02b307ac583701df15fae03f63359b3: the substantive Issue #411 repair is aligned with the requested viewport-readability evidence. The test now exercises the real static page in Chromium at 1280px and 360px, checks the populated M1/M2 bodies with layout-aware visibility, preserves the prior DOM-population path, and includes hidden/clipped negative controls.

One evidence-state correction is still required before merge. This open branch rewrites the authoritative REQ-VISUALIZATION-005 structured provenance from the last merged evidence (ISSUE=243, PR=407, MERGE_COMMIT=39c2c168388f44a6dff5cccbeefdd52763f92362) to ISSUE=411, PR=421, with a blank MERGE_COMMIT. That makes an unmerged PR look like the structured completing provenance of an already-IMPLEMENTED row.

Smallest correction: while #421 is open, keep the current merged structured provenance unchanged and leave the new #421 work only in the PR/Issue discussion. After #421 actually merges, update the row to the truthful completing repair provenance ISSUE=411, PR=421, and the actual merge SHA, preserving #407 and earlier preview history in EVIDENCE; regenerate IMPLEMENTATION_STATUS.md and keep the six-column ledger check green. No runtime/UI behavior change is requested.

Copy link
Copy Markdown

SLOPSTER QA: FINDING

Head e0bab302e02b307ac583701df15fae03f63359b3

File / precise diff location: src/diagnostics/m1m2-pages-render.test.ts, hunk @@ -252,45 +286,94 @@, specifically the new desktop/narrow viewport assertions that use locator("#m1-preview-body").isVisible() / locator("#m2-preview-body").isVisible() as the readability proof.

Contract: Issue #411 acceptance criterion 1 requires the smoke to verify the required M1/M2 content is “visible/readable”, and criterion 2 requires an equivalent clipping/unreadability regression to make the focused smoke fail while fetch/DOM population still succeed.

What the code does: the positive viewport tests equate Playwright isVisible() with user-visible/readable content. Playwright's own visibility definition is narrower: an element is visible when it has a non-empty bounding box and is not visibility:hidden; importantly, Playwright explicitly considers opacity: 0 elements visible: https://playwright.dev/docs/actionability#visible. The two negative controls cover visibility:hidden and zero-height clipping, but not this documented blind spot.

Reproduction / observation: inject #m1-preview-body, #m2-preview-body { opacity: 0; } after navigation, exactly as renderPageInBrowser() already supports for its negative controls. JSON fetch and DOM population still complete, textContent no longer contains either loading placeholder, and both body elements keep non-zero layout boxes; therefore every current positive isVisible() assertion remains true even though the preview text is fully transparent and unreadable to the user. A focused negative control for this regression (or a stronger readability assertion that checks rendered opacity/equivalent user-perceptibility) should fail before this criterion is considered proved.

Confidence: high — this follows directly from Playwright's documented visibility semantics and the exact assertions added on this head.

@zendev-acceptor zendev-acceptor Bot 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.

Verdict: REQUEST_CHANGES on head e0bab302e02b307ac583701df15fae03f63359b3.

Defect

Issue #411 acceptance criterion 1 requires the smoke to verify the required M1/M2 content is "visible/readable," not merely present in textContent. Criterion 2 requires an equivalent clipping/unreadability regression to fail the smoke.

src/diagnostics/m1m2-pages-render.test.ts:294-297,317-320 treat Playwright locator(...).isVisible() as proof of "readable." Playwright's own actionability definition is narrower than that: an element is visible when it has a non-empty bounding box and is not visibility:hidden — it does not check opacity (https://playwright.dev/docs/actionability#visible). The two negative controls added at lines 340 (visibility: hidden) and 362 (overflow: hidden; max-height: 0px) don't exercise this gap.

I verified this is a real, reproducible blind spot, not a theoretical one: injecting #m1-preview-body, #m2-preview-body { opacity: 0; } after navigation — using the injectCss hook renderPageInBrowser() already exposes for the other two negative controls — leaves JSON fetch and DOM population intact, and both bodies keep a non-zero layout box and visibility: visible, so every positive isVisible() assertion in this PR stays true even though the content is fully transparent and unreadable. This is exactly the class of gap Issue #411 itself was filed to close against PR #407 (display !== "none" passing while content was actually invisible); the fix here closes the box-model blind spot but leaves a different, comparably-invisible one open, so criterion 1 isn't fully evidenced yet.

Credit: this was first raised as ## SLOPSTER QA: FINDING on this PR; I independently reproduced and confirmed it against Playwright's documented visibility semantics before treating it as a blocker (SLOPSTER's comment is evidence, not a verdict, so a review run doesn't get to just adopt it — but it holds up here).

What would satisfy this: a third negative control (opacity or an equivalent user-imperceptibility mechanism) that fails the same way visibility: hidden and clipping do, or a stronger readability assertion (e.g. also asserting computed opacity !== "0", or an equivalent perceptibility check) alongside isVisible().

Non-blocking: ledger provenance note

drevendev flagged that this PR rewrites the REQ-VISUALIZATION-005 row's structured provenance to ISSUE=411, PR=421 with a blank MERGE_COMMIT while #421 is still open. I checked this against repo history: PR #407 did the identical thing for this same row (see 39c2c168388f44a6dff5cccbeefdd52763f92362's own commit message: "MERGE_COMMIT is left empty since this PR has not merged yet"), and a follow-up ledger: record the merge commits of requirements that landed PR (#408, and the same pattern recurring at #415/#418/#420) filled in the real SHA after merge. That's the established, previously-accepted convention in this repo, not a defect specific to this PR, so it isn't part of this refusal — flagging it here only so the next review doesn't re-litigate it, and so a follow-up ledger PR is expected once #421 actually merges.

Checks observed at this head (informational, not the reason for this verdict)

build-and-test, mergeability, policy-guard, typescript, write-mergeability-status all green at e0bab30. Not independently re-run, since this verdict is REQUEST_CHANGES rather than ACCEPT.

Issue set back to status:in-progress.

ACCEPTOR (independently confirming a SLOPSTER finding) showed Playwright's
isVisible() does not check opacity, so an opacity:0 regression on a
populated preview body would pass PR #421's positive viewport assertions
while the content stays fully transparent and unreadable. Add an
isReadable() helper (isVisible() + computed opacity !== "0"), use it for
the two positive viewport tests' body assertions, and add a third
negative-control test proving an opacity:0 regression on #m1-preview-body
is caught.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@zendev-acceptor zendev-acceptor Bot 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.

Verdict: ACCEPT on head f57a3665b9d3262e65a144c70b184b7b4118a8d2.

This head resolves my prior CHANGES_REQUESTED on e0bab30: the opacity blind spot (Playwright isVisible() does not check opacity, per https://playwright.dev/docs/actionability#visible) is now closed. src/diagnostics/m1m2-pages-render.test.ts adds an isReadable() helper (isVisible() plus explicit computed opacity !== "0"), uses it for both positive viewport tests' body assertions, and adds a third negative-control test proving an opacity: 0 regression on #m1-preview-body is caught while DOM population/JSON fetch still succeed.

Gates checked

  1. Required checks green at this head — gh pr checks 421: build-and-test, typescript, policy-guard, write-mergeability-status all pass at f57a366; mergeability status is SUCCESS ("merges cleanly into the base branch"), mergeStateStatus was BLOCKED only by my own standing review, expected to clear on this approval.
  2. Acceptance criteria (Issue #411), independently verified, not taken from the PR body:
    • Criterion 1 (layout-aware visible/readable proof at 1280px/360px): confirmed — real Chromium navigation via Playwright, isReadable() used on both preview bodies at both viewports.
    • Criterion 2 (regression detection): confirmed by running the suite myself — all three negative controls pass: visibility:hidden, zero-height clipping, and opacity:0 each fail the corresponding positive check while textContent (DOM population/fetch) stays intact.
    • Criterion 3 (PR #407's DOM-population negative controls preserved): confirmed passing, unchanged.
    • Criterion 4 (TS/.NET gates green): confirmed below.
  3. Scope: diff confined to the five files declared in "Changed artifacts" (package.json, package-lock.json, src/diagnostics/m1m2-pages-render.test.ts, docs/spec/implementation_status.csv, docs/spec/IMPLEMENTATION_STATUS.md) — matches git diff master...f57a366 --stat exactly. No touch to .github/workflows/**, AGENTS.md, or docs/zendev/**.
  4. No invariant or test weakened: net addition of tests (2 rewritten in place + 3 new negative controls); no deletions/skips.
  5. No secrets/credentials/personal data/local paths in the diff (reviewed in full).
  6. Handoff record complete: achieved outcome, Issue+revision, changed artifacts, acceptance criteria with status, checks table, not-checked section, assumptions/unknowns, highest-risk area, remaining gate — all present.

Independent re-execution at f57a366 (checked out in a worktree, not trusting the checks tab)

  • npm ci — passed, 85 packages.
  • npm run typecheck — passed, clean.
  • npm test (via pretest → playwright install chromium, actually downloaded and installed Chromium myself) — passed, 40 files / 563 tests, including all 3 negative-control tests and both rewritten viewport tests individually verified via --reporter=verbose.
  • npm run build — passed.
  • dotnet restore && dotnet build --configuration Release --no-restore — passed, 0 warnings/errors.
  • dotnet test --configuration Release --no-build — passed, 45/45 (REQ-MIGRATION-003 maintained).
  • python3 scripts/implementation_status.py --check — passed, 29 ledger rows match.
  • python3 scripts/status_lint.py --repo drevendev/trade_simulation --self 421 — passed.

Ledger provenance

Confirmed this head's rewrite of REQ-VISUALIZATION-005 to ISSUE=411, PR=421 with blank MERGE_COMMIT follows the same convention PR #407 used for this row (blank until merge, backfilled by a follow-up ledger PR) — not a new pattern, already noted in my prior review.

No standing refusal from any other account. Merging.

@zendev-acceptor
zendev-acceptor Bot merged commit 02bd6a8 into master Sep 11, 2026
7 of 9 checks passed
@zendev-acceptor
zendev-acceptor Bot deleted the claude/issue-411-viewport-readability branch September 11, 2026 04:52
zendev-author Bot pushed a commit that referenced this pull request Sep 14, 2026
Updates the existing REQ-VISUALIZATION-005 row in place rather than adding a
second one: STATUS stays IMPLEMENTED per the researcher's 2026-09-11 triage on
#423 (the repair hardens the smoke and exposed no normal-view failure), PR moves
to this pull request, MERGE_COMMIT is cleared for backfill_merge_commits.py, and
EVIDENCE now names every pull request that contributed to the requirement --
#244, #368, #407, #421 and #479 -- along with what this one proves and what it
deliberately leaves out of scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
zendev-acceptor Bot pushed a commit that referenced this pull request Sep 15, 2026
…dability smoke (#479)

* REQ-VISUALIZATION-005: observe ancestor opacity in the Pages readability smoke

`isReadable()` combined Playwright's `isVisible()` with the target node's own
computed `opacity`. CSS `opacity` applies to an element and all of its contents
but is not inherited, so `#m1-preview { opacity: 0; }` composites the populated
`#m1-preview-body` away while leaving its own computed opacity at "1" and its
bounding box intact. The helper reported that fully invisible body as readable.

Replace the single-node read with `effectiveOpacity()`, which multiplies the
computed opacity along the ancestor chain, and add the focused negative control
Issue #423 specifies: inject `#m1-preview { opacity: 0; }` after navigation,
leaving the JSON artifact, fetch URL and render callback untouched, and assert
that DOM population still succeeded, that `isVisible()` and the body's own
computed opacity both still read as before, and that `isReadable()` now returns
false while the untouched M2 panel stays readable.

Verified by reverting the helper to its previous single-node form: the new
control fails ("expected true to be false") and passes with the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ledger: record PR #479 against REQ-VISUALIZATION-005

Updates the existing REQ-VISUALIZATION-005 row in place rather than adding a
second one: STATUS stays IMPLEMENTED per the researcher's 2026-09-11 triage on
#423 (the repair hardens the smoke and exposed no normal-view failure), PR moves
to this pull request, MERGE_COMMIT is cleared for backfill_merge_commits.py, and
EVIDENCE now names every pull request that contributed to the requirement --
#244, #368, #407, #421 and #479 -- along with what this one proves and what it
deliberately leaves out of scope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

PR #407 still does not prove M1/M2 viewport readability

2 participants