Skip to content

Approval gate glyph: a state machine for the gate, zero-dep (#544) - #545

Open
richard-devbot wants to merge 1 commit into
mainfrom
claude/gate-glyph-499
Open

Approval gate glyph: a state machine for the gate, zero-dep (#544)#545
richard-devbot wants to merge 1 commit into
mainfrom
claude/gate-glyph-499

Conversation

@richard-devbot

@richard-devbot richard-devbot commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Closes #544. Last child of epic #499the epic completes at 8/8 with this.

Taken on Richardson's call: "if it brings advantages for our ui/ux then lets develop it."

Built zero-dep, and why that is not a substitution

The advantage being asked for is the glyph — a gate whose states are a machine, so the approval lifecycle reads as a mechanism rather than a colour change. Every objection I raised against the Rive child on #499 attaches to the runtime, not to that visual:

objection attaches to
unreviewable .riv binary the asset pipeline
a state machine holding its own copy of governance state the runtime
a canvas nothing in our suite can assert against the runtime

Authoring the same state machine in SVG + CSS keeps the advantage and loses all three. If Rive-the-tool is wanted for its own sake, the Studio is where those objections don't bite — that stays open as a separate call.

What it does

The shackle carries the meaning:

state shackle mark
pending shut
approved opens check draws
consumed shuts again check → spent bar
rejected shut cross draws

The consumed row is why this was worth building. A spent one-shot override is back to blocking, and nothing on the card said so as a mechanism — the copy explains it in a sentence, and after #541 the colour merely faded. A shackle that visibly re-closes states it in the way a reader absorbs without reading.

Correctness properties, not styling choices

Every part exists in every state, undrawn or transparent rather than absent. A part rendered only for its own state would be a new node at the flip, and a new node cannot transition — so the change would never animate on the one occasion it means something. That is the #541 Proof Rail lesson applied.

transition, never @keyframes. A card that arrives approved is still; only a card that flips animates. Motion never claims an event nobody watched happen. Both directions are asserted.

Transform, opacity and stroke-dash only — a browser test measures that the card neither resizes nor moves when the gate opens, because this list repaints and a shift would move the page under the reader's cursor. A unit test enumerates the transitioned properties and rejects anything outside that set.

aria-hidden — the pill beside it already states the status in words. A second announcement would be noise, not access.

Verification

  • 1973/1973 core, 59/59 browser (52 → 59), lint 0 errors, typecheck clean, validate 196 agents, security baseline OK, git diff --check clean.
  • Two unit tests carry vacuity guards: both iterate parts and continue when a rule is missing, so without the guards they would pass on an unstyled glyph. Caught before implementing — I ran the suite and saw them pass green against nothing.
  • Browser: per-state geometry read on fresh nodes (re-classing one node and reading immediately samples mid-transition — the Motion: Proof Rail transitions, approval gate lifecycle, scroll reveal (#499 remaining zero-dep children) #541 lesson), the flip asserted on a provably surviving node, arrival asserted still, reduced motion, card-geometry stability, axe-clean, zero page errors.
  • Screenshots taken and read, per the rule this wave keeps re-learning.

Not built

The Rive runtime and any .riv asset. Empty-state illustrations — declined on #499: our empty states are deliberately informative ("No telemetry yet", "No runs in this scope") and an illustration adds warmth but no information.

Summary by CodeRabbit

  • New Features

    • Added visual approval gate glyphs to approval cards.
    • Glyphs clearly indicate pending, approved, rejected, and consumed states.
    • State changes include smooth visual transitions and reduced-motion support.
  • Accessibility

    • Approval glyphs are decorative and do not interfere with assistive technologies.
    • Improved visual behavior maintains stable card layouts.

Last child of epic #499, built zero-dep instead of on the Rive runtime.

The advantage asked for is the GLYPH — a gate whose states are a machine, so
the approval lifecycle reads as a mechanism. The objections raised against
the Rive child all attach to the RUNTIME rather than that visual: an
unreviewable .riv binary, a state machine holding its own copy of governance
state, and a canvas nothing in the suite can assert against. Authoring the
same machine in SVG + CSS keeps the advantage and loses all three.

The shackle carries the meaning:

  pending   shut          the gate is closed, awaiting a human
  approved  OPENS + check passage granted
  consumed  SHUTS AGAIN   the one attempt was spent; blocking resumed
  rejected  shut + cross  refused

The consumed row is why this was worth building. A spent one-shot override
is back to blocking, and nothing on the card said so as a mechanism — the
copy explained it in a sentence and the colour just faded. A shackle that
visibly re-closes states it in the way a reader absorbs without reading.

Every part is present in every state, undrawn or transparent rather than
absent: a part rendered only for its own state would be a new node at the
flip, and a new node cannot transition (the #541 Proof Rail lesson). And
transition rather than animation means a card that ARRIVES approved is
still, while only a real flip animates — motion never claims an event that
did not happen.

Transform, opacity and stroke-dash only; a browser test asserts the card
neither resizes nor moves when the gate opens. aria-hidden, because the pill
beside it already states the status in words.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@strix-security

strix-security Bot commented Aug 1, 2026

Copy link
Copy Markdown

Strix is installed on this repository, but we couldn't run this PR security review because this workspace's trial has ended. Add a card to resume code reviews here.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Approval cards now include an accessibility-hidden SVG gate glyph. CSS maps pending, approved, rejected, and consumed states to distinct gate visuals and transitions. Unit-style and browser tests validate rendering, state changes, motion behavior, geometry, and accessibility.

Changes

Approval gate glyph

Layer / File(s) Summary
Glyph rendering and state styling
src/observability/dashboard/ui/pages/approvals.js, src/observability/dashboard/ui/styles.js
Approval cards render persistent SVG gate parts. CSS styles pending, approved, rejected, and consumed states with shackle, mark, color, opacity, and transform changes.
Glyph behavior validation
tests/hub-gate-glyph-544.test.js, tests/browser/dashboard-544-gate-glyph.test.js
Tests cover state rendering, transitions, reduced motion, geometry, accessibility, and card integration.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ApprovalPage
  participant ApprovalCard
  participant CSSStateRules
  ApprovalPage->>ApprovalCard: render approval status and gate glyph
  ApprovalCard->>CSSStateRules: apply approval state class
  CSSStateRules-->>ApprovalCard: update shackle, marks, colors, and spent bar
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: richardsongunde

Poem

A rabbit sees the gate glow bright,
The shackle opens, marks take flight.
A spent gate closes once again,
Crosses guard the blocked domain.
SVG hops through every state.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the approval gate glyph and its zero-dependency state-machine implementation.
Linked Issues check ✅ Passed The changes implement the SVG/CSS state machine, required states, transitions, accessibility behavior, and verification tests for [#544].
Out of Scope Changes check ✅ Passed The code and tests remain within the linked issue scope, with no unrelated features, Rive runtime, or .riv assets added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/gate-glyph-499

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Approvals: add SVG gate glyph state machine for approval lifecycle

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add zero-dependency SVG+CSS “gate” glyph that animates approval state transitions.
• Render glyph beside the existing status pill to better convey consumed/one-shot overrides.
• Add unit + real-browser tests to enforce transition-only, no-reflow, and a11y invariants.
Diagram

graph TD
  A["Approvals page (approvals.js)"] --> B["approvalHtml()"] --> C["gateGlyphHtml(status)"] --> D["Inline SVG parts (shackle/check/cross/bar)"] --> E["CSS state rules (styles.js)"] --> F["Tests (unit + browser)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use Rive runtime/asset pipeline
  • ➕ Designer-friendly authoring environment and richer motion tooling
  • ➕ Potential reuse of existing Rive animation workflows
  • ➖ Binary assets reduce reviewability and diff quality
  • ➖ Runtime introduces a second state machine that can drift from app governance state
  • ➖ Canvas-based rendering is harder to assert in the existing test suite
2. Static icon set per status (no transitions)
  • ➕ Simplest implementation and lowest CSS complexity
  • ➕ Minimal risk of unexpected motion
  • ➖ Loses the key UX benefit: the lifecycle reads as a mechanism (especially consumed re-closing)
  • ➖ Harder to communicate ‘spent override’ without motion semantics
3. SMIL or @keyframes-based SVG animation
  • ➕ Can express complex drawings compactly
  • ➕ Potentially less per-state CSS
  • ➖ Violates the PR’s contract: arrivals would animate and imply unseen events
  • ➖ More brittle across browsers and harder to constrain to non-layout properties

Recommendation: The chosen SVG+CSS transition approach is the best fit for the stated constraints (reviewability, zero-dep runtime, and assertable behavior). The tests meaningfully lock in the intended invariants (node persistence, transition-only motion, no layout shift, decorative a11y), making this preferable to Rive or keyframe-driven animation for this surface.

Files changed (4) +394 / -1

Enhancement (2) +72 / -1
approvals.jsRender approval state glyph next to the status pill +27/-1

Render approval state glyph next to the status pill

• Introduces gateGlyphHtml(status) that emits a persistent-part SVG glyph (shackle/body/check/cross/spent bar) and marks it aria-hidden. Updates approval card rendering to include the glyph beside the existing textual pill within a new .approval-state wrapper.

src/observability/dashboard/ui/pages/approvals.js

styles.jsAdd CSS-driven gate glyph state machine and transitions +45/-0

Add CSS-driven gate glyph state machine and transitions

• Adds styling for the glyph container and SVG parts, using only transitions (no keyframes) and relying on stroke-dashoffset/opacity/transform changes. Defines per-status rules so approved opens the shackle and draws the check, rejected draws the cross, and consumed re-closes the shackle and swaps the check for a spent bar without affecting layout geometry.

src/observability/dashboard/ui/styles.js

Tests (2) +322 / -0
dashboard-544-gate-glyph.test.jsBrowser tests for glyph behavior, motion rules, and geometry stability +179/-0

Browser tests for glyph behavior, motion rules, and geometry stability

• Adds real-browser coverage to verify the shackle open/close semantics across states, mark visibility, and that transitions occur on surviving nodes (flip) but do not replay on arrival. Also asserts reduced-motion behavior, zero geometry shift on approval, and runs axe to ensure no serious/critical a11y regressions (when axe-core is available).

tests/browser/dashboard-544-gate-glyph.test.js

hub-gate-glyph-544.test.jsUnit tests for glyph DOM invariants and allowed transitioned properties +143/-0

Unit tests for glyph DOM invariants and allowed transitioned properties

• Adds unit tests that validate every SVG part exists in every state, the glyph is decorative (aria-hidden), and that the card renders glyph + pill together. Parses styles to assert the shackle opens on approved and re-closes on consumed, each state reveals only its own mark, no @keyframes are used, and only a constrained set of properties are transitioned to avoid reflow risk.

tests/hub-gate-glyph-544.test.js

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/browser/dashboard-544-gate-glyph.test.js`:
- Around line 106-116: Update the setup around the flip evaluation in
dashboard-544-gate-glyph.test.js to stop overriding window.handleGlobalSnapshot,
since the dashboard uses a module-local handler. Use an interception mechanism
shared with the dashboard client, or invoke the client’s render path directly
during setup, so live snapshot rendering is reliably paused before mutating the
approval card.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 814d3e42-123b-42d5-88fd-b4b43f93a1cb

📥 Commits

Reviewing files that changed from the base of the PR and between 905e758 and e98423c.

📒 Files selected for processing (4)
  • src/observability/dashboard/ui/pages/approvals.js
  • src/observability/dashboard/ui/styles.js
  • tests/browser/dashboard-544-gate-glyph.test.js
  • tests/hub-gate-glyph-544.test.js

Comment on lines +106 to +116
const flip = await page.evaluate(() => {
window.handleGlobalSnapshot = function () {}; // hold live ticks
const card = document.querySelector('#page-approvals .approval-card');
card.querySelector('.gate-check').__rs544 = 'original';
card.className = 'approval-card approved';
const check = card.querySelector('.gate-check');
return {
sameNode: check.__rs544 === 'original',
offsetAtFlip: parseFloat(getComputedStyle(check).strokeDashoffset),
};
});

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the definition and all call sites of the snapshot handler.
fd -t f -e js . src/observability/dashboard/ui | xargs rg -n -C4 'handleGlobalSnapshot'

# Check whether it is assigned onto window/globalThis anywhere.
rg -n -C3 --type=js 'window\.handleGlobalSnapshot|globalThis\.handleGlobalSnapshot'

# Check whether other browser tests already rely on this override.
fd -t f -e js . tests/browser | xargs rg -n 'handleGlobalSnapshot'

Repository: richard-devbot/SDLC-rstack

Length of output: 1459


🏁 Script executed:

#!/bin/bash
set -u

echo "== client.js relevant lines =="
sed -n '388,432p' src/observability/dashboard/ui/client.js | cat -n

echo
echo "== all handleGlobalSnapshot references =="
rg -n -C3 'handleGlobalSnapshot' .

echo
echo "== browser test references =="
rg -n -C2 'handleGlobalSnapshot' tests/browser || true

echo
echo "== window/globalThis assignments matching handleGlobalSnapshot =="
rg -n --type-add 'jsfile':\*.js --type-add 'jsfile':\*.mjs --type-add 'jsfile':\*.ts --type-add 'jsfile':\*.tsx \
  '(\b(window|globalThis)\s*\.)\s*handleGlobalSnapshot\s*=' . || true

python3 - <<'PY'
from pathlib import Path
import re

paths = [Path(x) for x in Path('.').glob('src/**/*.js') if Path(x).is_file()]
paths += [Path(x) for x in Path('.').glob('**/*.js') if Path(x).is_file()][len(paths):]
for path in Path('.').rglob('*'):
    if str(path).startswith(('.git', 'node_modules', 'dist', 'build')) or path.is_dir():
        continue
    if not path.suffix in {'.js','.mjs','.ts','.tsx'}:
        continue
    try:
        text = path.read_text(encoding='utf-8', errors='ignore')
    except Exception:
        continue
    lines = text.splitlines()
    for i,line in enumerate(lines,1):
        if 'handleGlobalSnapshot' in line:
            print(f'[{path}:{i}]\t{line.strip()}')
        if re.search(r'(window|globalThis)\s*\.\s*handleGlobalSnapshot\s*=', line):
            print(f'ASSIGNMENT [{path}:{i}]\t{line.strip()}')

PY

Repository: richard-devbot/SDLC-rstack

Length of output: 18639


Remove the window.handleGlobalSnapshot override assumption.

handleGlobalSnapshot is a module-local function invoked by the dashboard client through handleGlobalSnapshot(JSON.parse(event.data)), so replacing window.handleGlobalSnapshot does not intercept live snapshot renders. Use an interception mechanism shared by the dashboard and test, or render the client directly from this file during setup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/browser/dashboard-544-gate-glyph.test.js` around lines 106 - 116,
Update the setup around the flip evaluation in dashboard-544-gate-glyph.test.js
to stop overriding window.handleGlobalSnapshot, since the dashboard uses a
module-local handler. Use an interception mechanism shared with the dashboard
client, or invoke the client’s render path directly during setup, so live
snapshot rendering is reliably paused before mutating the approval card.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (2)

Context used
✅ Compliance rules (platform): 300 rules
✅ Skills: 17 invoked
  code-review-pr
  claude-api
  documentation-writing
  pptx
  docx
  performance-monitoring
  security-compliance
  cso
  plan-eng-review
  design-review
  prompt-engineering
  mcp-builder
  qa-testing
  code-patterns
  xlsx
  security-owasp
  testing-qa

Grey Divider


Remediation recommended

1. Unguarded DOM dereferences 🐞 Bug ⚙ Maintainability
Description
The new Playwright suite dereferences querySelector() results inside page.evaluate() (e.g.,
cloneNode/getComputedStyle) without checking for null, so selector/fixture regressions will fail as
TypeErrors instead of actionable assertions. This makes CI failures harder to diagnose and slower to
fix.
Code

tests/browser/dashboard-544-gate-glyph.test.js[R52-60]

+  const card = document.querySelector('#page-approvals .approval-card');
+  const out = {};
+  for (const state of wanted) {
+    const clone = card.cloneNode(true);
+    clone.className = 'approval-card ' + state;
+    card.parentNode.appendChild(clone);
+    const shackle = clone.querySelector('.gate-shackle');
+    const style = getComputedStyle(shackle);
+    out[state] = {
Relevance

●●● Strong

Null-guard/assertion suggestions in page.evaluate were definitely accepted to avoid TypeErrors in
browser tests.

PR-#534
PR-#528

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new test code calls methods on the result of document.querySelector(...) without checking it
exists, so any DOM/fixture mismatch becomes an uninformative runtime exception inside the browser
context. This matches a previously accepted bug pattern in the same test area where explicit
assertions were recommended to avoid TypeErrors.

tests/browser/dashboard-544-gate-glyph.test.js[51-70]
tests/browser/dashboard-544-gate-glyph.test.js[103-116]
PR-#534

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new browser tests in `dashboard-544-gate-glyph.test.js` directly dereference DOM nodes returned by `querySelector()` inside `page.evaluate()`. If the fixture changes or the DOM structure regresses, the test will throw an unhelpful `TypeError` (e.g., calling `cloneNode` on null) rather than failing with a clear assertion about which element is missing.

### Issue Context
This repo has had prior review guidance to harden browser tests against null DOM lookups so failures are actionable (avoid TypeErrors inside `page.evaluate`).

### Fix Focus Areas
- tests/browser/dashboard-544-gate-glyph.test.js[51-70]
- tests/browser/dashboard-544-gate-glyph.test.js[103-116]
- tests/browser/dashboard-544-gate-glyph.test.js[152-164]

### Suggested change
Inside each `page.evaluate()`:
- Assert required nodes exist before dereferencing (e.g., `if (!card) throw new Error('...')`).
- Optionally add a small helper like `must(sel)` that throws a descriptive error when a selector is missing, and use it for `.approval-card`, `.gate-check`, `.gate-shackle`, `.gate-glyph`, etc.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Glyph transitions non-allowed properties 📜 Skill insight ➹ Performance
Description
The new gate glyph transitions stroke, fill, and stroke-dashoffset, but the compliance rule
allows animations/transitions only for transform and opacity. This can create performance and
rendering risks beyond the allowed animation surface.
Code

src/observability/dashboard/ui/styles.js[R1176-1188]

+  transition: transform var(--motion-base) var(--motion-ease-emphatic), stroke var(--motion-base) var(--motion-ease);
+}
+.gate-body { fill: var(--panel); stroke: var(--muted); stroke-width: 2.2; transition: stroke var(--motion-base) var(--motion-ease), fill var(--motion-base) var(--motion-ease); }
+.gate-check {
+  fill: none; stroke: var(--green); stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
+  stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0;
+  transition: stroke-dashoffset var(--motion-slow) var(--motion-ease-emphatic), opacity var(--motion-fast) var(--motion-ease);
+}
+.gate-cross {
+  fill: none; stroke: var(--red); stroke-width: 2.6; stroke-linecap: round;
+  stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0;
+  transition: stroke-dashoffset var(--motion-base) var(--motion-ease-emphatic), opacity var(--motion-fast) var(--motion-ease);
+}
Relevance

● Weak

“Only transform/opacity transitions” suggestions were definitely rejected; team keeps
stroke/fill/dash transitions.

PR-#542
PR-#528
PR-#539

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1399699 restricts animated/transitioned properties to transform and opacity.
The new glyph CSS explicitly transitions stroke, fill, and stroke-dashoffset in
src/observability/dashboard/ui/styles.js.

src/observability/dashboard/ui/styles.js[1173-1188]
Skill: design-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The gate glyph CSS transitions properties other than `transform` and `opacity` (notably `stroke`, `fill`, and `stroke-dashoffset`), violating the rule that only `transform` and `opacity` may be animated.

## Issue Context
Current rules animate the check/cross drawing via `stroke-dashoffset` and also transition `stroke`/`fill` colors. Compliance requires restricting the animated surface to `transform` and `opacity` only.

## Fix Focus Areas
- src/observability/dashboard/ui/styles.js[1173-1188]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. gateGlyphHtml not verb-noun 📜 Skill insight ⚙ Maintainability
Description
The newly added function gateGlyphHtml does not follow a verb-noun naming pattern as required.
This reduces consistency and readability across the JS codebase.
Code

src/observability/dashboard/ui/pages/approvals.js[R67-76]

+function gateGlyphHtml(status) {
+  return '<span class="gate-glyph ' + esc(status || 'pending') + '" aria-hidden="true">' +
+    '<svg viewBox="0 0 32 32" focusable="false">' +
+      '<path class="gate-shackle" d="M10 14v-3a6 6 0 0 1 12 0v3" />' +
+      '<rect class="gate-body" x="7" y="14" width="18" height="14" rx="3" />' +
+      '<path class="gate-check" pathLength="1" d="M11.5 21.2l3 3 6-6" />' +
+      '<path class="gate-cross" pathLength="1" d="M12 17.5l8 8M20 17.5l-8 8" />' +
+      '<path class="gate-bar" d="M11.5 21h9" />' +
+    '</svg></span>';
+}
Relevance

● Weak

Verb-noun rename suggestions were definitely rejected (helper renames), showing naming pattern isn’t
enforced.

PR-#534

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1399694 requires function names to follow a verb-noun naming pattern. The added
function is named gateGlyphHtml, which begins with a noun instead of a verb.

src/observability/dashboard/ui/pages/approvals.js[67-76]
Skill: code-patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new function name `gateGlyphHtml` does not start with a verb (verb-noun pattern required).

## Issue Context
This function generates HTML markup for the approval gate glyph; a verb-led name like `renderGateGlyphHtml` or `getGateGlyphHtml` would satisfy the naming convention.

## Fix Focus Areas
- src/observability/dashboard/ui/pages/approvals.js[67-76]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Gate glyph tests lack screenshots 📘 Rule violation ▣ Testability
Description
The new browser UI tests verify geometry and computed styles but do not perform any screenshot-based
visual comparison, so visual regressions (misalignment, clipping, stroke rendering differences)
could slip through. This violates the requirement that UI tests include screenshot-based visual
verification for layout/visual correctness.
Code

tests/browser/dashboard-544-gate-glyph.test.js[R152-167]

+glyphTest('the glyph never changes the card geometry (#544)', async ({ page, server }) => {
+  await openApprovals(page, server);
+  const box = await page.evaluate(() => {
+    const card = document.querySelector('#page-approvals .approval-card');
+    const before = card.getBoundingClientRect();
+    card.className = 'approval-card approved';
+    const after = card.getBoundingClientRect();
+    const glyph = card.querySelector('.gate-glyph').getBoundingClientRect();
+    return {
+      shifted: Math.abs(after.height - before.height) + Math.abs(after.top - before.top),
+      glyphW: Math.round(glyph.width), glyphH: Math.round(glyph.height),
+    };
+  });
+  assert.equal(box.shifted, 0, 'the card does not resize or move when the gate opens');
+  assert.ok(box.glyphW >= 20 && box.glyphH >= 20, `the glyph is large enough to read — ${box.glyphW}x${box.glyphH}`);
+});
Relevance

● Weak

Screenshot-based visual verification requests for UI/browser motion tests were definitely rejected.

PR-#542

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 2448668 requires screenshot-based visual verification for UI/layout correctness
tests. The added test the glyph never changes the card geometry (#544) performs bounding-box
assertions but contains no screenshot capture or comparison step.

Rule 2448668: UI tests must include screenshot-based visual verification
tests/browser/dashboard-544-gate-glyph.test.js[152-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New Playwright browser tests for the approvals gate glyph assert layout/visual properties without any screenshot-based comparison, which violates the requirement for screenshot-based visual verification in UI tests.

## Issue Context
`tests/browser/dashboard-544-gate-glyph.test.js` includes layout/visual checks (e.g., bounding boxes) but never captures a screenshot and compares it to a committed baseline.

## Fix Focus Areas
- tests/browser/dashboard-544-gate-glyph.test.js[152-167]

## Implementation notes
- Capture a deterministic screenshot of the relevant region (e.g., the first `.approval-card` or `#page-approvals`) after the page is settled.
- Add an automated comparison against a committed baseline (e.g., by hashing the screenshot buffer with SHA-256 and asserting it equals an `EXPECTED_*_SCREENSHOT_HASH`, or by introducing a lightweight pixel-diff/snapshot mechanism used by the repo).
- Ensure the screenshot is taken in a stable state (fonts loaded, fixed viewport) to reduce flakiness.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +52 to +60
const card = document.querySelector('#page-approvals .approval-card');
const out = {};
for (const state of wanted) {
const clone = card.cloneNode(true);
clone.className = 'approval-card ' + state;
card.parentNode.appendChild(clone);
const shackle = clone.querySelector('.gate-shackle');
const style = getComputedStyle(shackle);
out[state] = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

4. Unguarded dom dereferences 🐞 Bug ⚙ Maintainability

The new Playwright suite dereferences querySelector() results inside page.evaluate() (e.g.,
cloneNode/getComputedStyle) without checking for null, so selector/fixture regressions will fail as
TypeErrors instead of actionable assertions. This makes CI failures harder to diagnose and slower to
fix.
Agent Prompt
### Issue description
The new browser tests in `dashboard-544-gate-glyph.test.js` directly dereference DOM nodes returned by `querySelector()` inside `page.evaluate()`. If the fixture changes or the DOM structure regresses, the test will throw an unhelpful `TypeError` (e.g., calling `cloneNode` on null) rather than failing with a clear assertion about which element is missing.

### Issue Context
This repo has had prior review guidance to harden browser tests against null DOM lookups so failures are actionable (avoid TypeErrors inside `page.evaluate`).

### Fix Focus Areas
- tests/browser/dashboard-544-gate-glyph.test.js[51-70]
- tests/browser/dashboard-544-gate-glyph.test.js[103-116]
- tests/browser/dashboard-544-gate-glyph.test.js[152-164]

### Suggested change
Inside each `page.evaluate()`:
- Assert required nodes exist before dereferencing (e.g., `if (!card) throw new Error('...')`).
- Optionally add a small helper like `must(sel)` that throws a descriptive error when a selector is missing, and use it for `.approval-card`, `.gate-check`, `.gate-shackle`, `.gate-glyph`, etc.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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.

Approval gate glyph: a state machine for the gate, zero-dep (#499 last child)

2 participants