Skip to content

feat(#502): automated copy & duplication audit — static lint, projection invariants, browser sweep - #538

Merged
richard-devbot merged 1 commit into
mainfrom
copy-audit-tooling-502
Aug 1, 2026
Merged

feat(#502): automated copy & duplication audit — static lint, projection invariants, browser sweep#538
richard-devbot merged 1 commit into
mainfrom
copy-audit-tooling-502

Conversation

@richard-devbot

Copy link
Copy Markdown
Owner

Summary

All three layers the issue asked for. Verified from scratch that none existed on disk despite the issue body's pre-checked acceptance boxes (standard lesson — pre-checked issue checkboxes are unverified until proven).

Layer 1 — static style/copy lint (tests/hub-copy-lint-502.test.js): pins the #492 fix (the global [hidden] { display: none !important; } guard must exist, and no other display:...!important rule may shadow it) and scans ui/pages/*.js for duplicate user-facing prose within one module, with a documented allowlist for designed repetition.

Layer 2 — projection invariant tests (tests/hub-projection-invariants-502.test.js): sweeps the general property across every alert type / readiness outcome / approval-dedup id-shape, rather than one historical scenario at a time (which #491/#493/#494 already pin individually). This found two real bugs:

  1. The "High failure rate" alert still truncated its run id via .slice(-12) — a third occurrence of the Alerts: stalled-run detail mangles run id via slice(-12) and prints raw minutes #491 bug class the original fix wave missed.
  2. approvalRequestsFromBlockedGates deduped by a project-scoped key: but an UNSCOPED id: — two gates in different projects sharing the same run/task/artifact string incorrectly suppressed each other, a residual instance of the CWE-863 cross-project-suppression class Strix found in PR Approvals: one decision = one pending item — semantic dedup + realpath-canonical roots (#494, #505) #509.

Both fixed and mutation-tested (reverting each reproduces the real failure).

Layer 3 — browser copy-audit sweep (tests/browser/dashboard-copy-audit-502.test.js): extends the #96 harness — extracts visible text from the real painted DOM across all six destinations, fails on an undocumented exact-duplicate within one page, reports (doesn't gate on) near-duplicates across pages. Found that a naive extraction flagged data values (paths, timestamps, a project-id hash derived from the fixture's own random temp dir) as "duplicates" — excluded these structurally rather than allowlisting them (an exact-string entry for something random-per-run would go stale immediately). Remaining real findings are genuinely designed repetition, documented in the allowlist.

Verification

Test plan

  • npm test — 1929/1929
  • npm run test:browser — 34/34
  • npm run typecheck / npm run lint — 0 errors
  • node scripts/security-audit.mjs / npm run validate — clean
  • Every layer mutation-tested

🤖 Generated with Claude Code

…ion invariants, browser sweep

All three layers the issue asked for, verified from scratch since none
existed on disk despite the issue body's pre-checked acceptance boxes.

Layer 1 — static style/copy lint (tests/hub-copy-lint-502.test.js):
- Pins the #492 fix: the global `[hidden] { display: none !important; }`
  guard must exist verbatim, and no OTHER `display: ...!important` rule may
  exist (the only thing that can shadow an !important rule for the same
  property is another one).
- Scans ui/pages/*.js for duplicate user-facing prose within one module (a
  regex-based heuristic distinguishing sentence-like copy from HTML
  fragments, CSS class lists, and SVG path data — refined iteratively
  against the real codebase until it produced zero false positives).
  Undocumented duplicates fail; a documented allowlist
  (tests/helpers/copy-audit-allowlist.json) names why each real one is
  designed repetition. A third test guards the allowlist itself against
  staleness.

Layer 2 — projection invariant tests (tests/hub-projection-invariants-502.test.js):
- Sweeps the general PROPERTY across every alert type / every readiness
  outcome / several approval-dedup id-shapes, rather than pinning one
  historical scenario the way #491/#493/#494's own tests already do.
  This found two REAL bugs neither of those specific tests had exercised:
    1. src/observability/alerts/engine.js: the "High failure rate" alert
       still truncated its run id via `.slice(-12)` — a third occurrence
       of the #491 bug class the original fix wave missed. Fixed.
    2. src/observability/dashboard/state/approvals.js:
       approvalRequestsFromBlockedGates deduped by a project-scoped `key:`
       AND an UNSCOPED `id:` (approvalQueueId doesn't include project) — so
       two gates in DIFFERENT projects sharing the same run id/task id/
       artifact string (plausible: these are canonical stage conventions
       shared across every project) incorrectly suppressed each other via
       the bare id collision alone, a residual instance of the exact
       CWE-863 cross-project-suppression class Strix found and partially
       fixed in PR #509. Fixed by scoping the id-based dedup key the same
       way the semantic key already was.
  Both fixes mutation-tested (reverting each reproduces the real failure).

Layer 3 — browser copy-audit sweep (tests/browser/dashboard-copy-audit-502.test.js):
- Extends the #96 browser-regression harness: extracts visible text blocks
  from the real painted DOM (real Chromium via playwright-core) across all
  six destinations, fails on an undocumented exact-duplicate block within
  one page, and reports (does not gate on) near-duplicates across pages.
- Found that a naive extraction flagged data VALUES (file paths, rendered
  timestamps, a project id hash derived from the fixture's own — random
  per test run — temp directory) as "duplicates." Excluded these
  structurally in the test itself rather than allowlisting them, since an
  exact-string allowlist entry for something random-per-run would go stale
  on the very next test run. The remaining real findings (stage ids shown
  in both a list and detail view, per-row fallback text, shared CTA
  copy) are genuinely designed repetition, documented in the same
  allowlist file under a browser_within_page key.
- Mutation-tested (an injected genuine duplicate is caught).

Verified: 1929/1929 core tests, 34/34 browser tests (including 12
pre-existing suites from #96/#525/#526/#533 unaffected), typecheck/lint/
security-audit/validate all clean.

Co-Authored-By: Claude Sonnet 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

Warning

Review limit reached

@richard-devbot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e1ca99d-9fda-4363-b51d-6533124a666e

📥 Commits

Reviewing files that changed from the base of the PR and between 7cd44bf and 31fa7a8.

📒 Files selected for processing (6)
  • src/observability/alerts/engine.js
  • src/observability/dashboard/state/approvals.js
  • tests/browser/dashboard-copy-audit-502.test.js
  • tests/helpers/copy-audit-allowlist.json
  • tests/hub-copy-lint-502.test.js
  • tests/hub-projection-invariants-502.test.js

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

Automated copy/duplication audit: static lint, projection invariants, browser sweep

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add three-layer copy/duplication audit: static lint, projection invariants, browser DOM sweep.
• Fix failure-rate alert copy to stop truncating run IDs; pin via invariant sweep.
• Scope approval dedup bookkeeping by project to prevent cross-project suppression.
Diagram

graph TD
CI[["CI / test runner"]] --> L1[["Layer 1: Static copy lint"]] --> AL[("Copy allowlist JSON")] --> UISRC["Dashboard UI sources"]
UISRC --> L2[["Layer 2: Projection invariants"]] --> PROJ["Projection code"]
PROJ --> L3[["Layer 3: Browser copy sweep"]] --> DOM["Dashboard server + DOM"]
subgraph Legend
direction LR
_test[["Test suite"]] ~~~ _data[("Allowlist JSON")] ~~~ _code["Prod code"]
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Parse CSS/JS with AST instead of regex heuristics
  • ➕ More robust detection of string literals and CSS rules
  • ➕ Fewer heuristic edge cases (escaped strings, nested templates, complex selectors)
  • ➖ Adds parsing dependencies and maintenance surface
  • ➖ May conflict with the repo’s existing “pure Node” test stance and increase runtime
2. Centralize dashboard copy into shared constants/i18n keys
  • ➕ Eliminates accidental within-module duplication structurally
  • ➕ Makes intentional reuse explicit and reviewable in one place
  • ➖ Large refactor across many ui/pages modules
  • ➖ Can reduce local readability for small, page-specific copy
3. Extract visible text via computed visibility/a11y tree in Playwright
  • ➕ Closer to “painted” user-visible text; fewer hidden-node false positives
  • ➕ Potentially more stable signal for duplicate detection
  • ➖ More complex and potentially slower browser-side extraction
  • ➖ May introduce flakiness across browser versions and CSS changes

Recommendation: The layered approach in this PR is the best fit: it pins known regression classes (CSS [hidden] guard, projection invariants) while adding an end-to-end rendered-text sweep for the copy-duplication class. Consider upgrading the browser extractor to explicitly filter non-visible nodes (computed style/aria tree) if false positives appear over time, but the current allowlist + structural exclusion strategy is a reasonable balance of rigor and maintainability.

Files changed (6) +483 / -4

Bug fix (2) +26 / -4
engine.jsStop truncating run IDs in failure-rate alert detail +7/-1

Stop truncating run IDs in failure-rate alert detail

• Removes the remaining '.slice(-12)' run-id truncation in the "High failure rate" alert detail string. Adds contextual comments tying the fix to the new projection-invariant sweep that exposed this missed site.

src/observability/alerts/engine.js

approvals.jsScope approval dedup bookkeeping by project to avoid cross-project collisions +19/-3

Scope approval dedup bookkeeping by project to avoid cross-project collisions

• Fixes approval request deduplication to include project scope when tracking existing queue IDs. Prevents gates in different projects with identical {runId, taskId, artifact} from suppressing each other while keeping emitted request IDs unchanged.

src/observability/dashboard/state/approvals.js

Tests (4) +457 / -0
dashboard-copy-audit-502.test.jsAdd Playwright-based within-page copy duplication audit across dashboard destinations +137/-0

Add Playwright-based within-page copy duplication audit across dashboard destinations

• Introduces a browser suite that extracts text blocks from each dashboard destination and fails on undocumented exact duplicates within a page. Also logs (non-gating) cross-page repeated text, and structurally excludes dynamic values (paths/timestamps/temp-derived ids) to avoid unstable allowlisting.

tests/browser/dashboard-copy-audit-502.test.js

copy-audit-allowlist.jsonDocument designed repetition allowlists for static and browser copy audits +50/-0

Document designed repetition allowlists for static and browser copy audits

• Adds a shared JSON allowlist used by both static duplicate-prose scanning and the browser within-page duplication gate. Each entry records the exact text plus a rationale, keeping intentional repetition explicit and reviewable.

tests/helpers/copy-audit-allowlist.json

hub-copy-lint-502.test.jsAdd static lint tests for [hidden] CSS guard and duplicate prose in ui/pages +129/-0

Add static lint tests for [hidden] CSS guard and duplicate prose in ui/pages

• Adds Node-only tests that (1) pin the global '[hidden] { display: none !important; }' rule and reject other 'display: ... !important' declarations, and (2) scan 'src/observability/dashboard/ui/pages/*.js' for duplicate user-facing prose with an allowlist. Includes a guard that fails on stale allowlist entries.

tests/hub-copy-lint-502.test.js

hub-projection-invariants-502.test.jsAdd projection invariant sweeps across alerts, overview, and approvals dedup +141/-0

Add projection invariant sweeps across alerts, overview, and approvals dedup

• Adds fixture-driven property tests that sweep multiple alert types (no truncated run-id fragments, no raw minute dumps), all readiness outcomes (rationale != title), and multiple approval id-shapes (queue record suppresses gate-derived duplicate; distinct decisions never collide). These tests directly pin the production fixes included in this PR.

tests/hub-projection-invariants-502.test.js

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (3)

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. Hidden text counted visible 🐞 Bug ☼ Reliability
Description
extractTextBlocks() collects every text node under #page-${id} without checking whether the node
is actually visible, so hidden tab panels (e.g., run-workspace) can contribute text and trigger
false duplicate failures or flakes. This contradicts the test’s stated intent (“painted DOM”) and
will become brittle as hidden panels gain more copy.
Code

tests/browser/dashboard-copy-audit-502.test.js[R60-71]

+async function extractTextBlocks(page, pageId) {
+  return page.evaluate((id) => {
+    const root = document.getElementById(`page-${id}`);
+    if (!root) return [];
+    const blocks = [];
+    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
+    let node;
+    while ((node = walker.nextNode())) {
+      const text = node.textContent.replace(/\s+/g, ' ').trim();
+      if (text) blocks.push(text);
+    }
+    return blocks;
Relevance

●● Moderate

No direct historical evidence found for requiring visibility filtering when extracting text nodes in
browser DOM sweeps.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test’s extraction walks all text nodes and does not filter out hidden descendants, while the UI
explicitly keeps hidden tab panels in the DOM via hidden. Therefore the test can count non-painted
text as if it were visible.

tests/browser/dashboard-copy-audit-502.test.js[60-73]
src/observability/dashboard/ui/pages/run-workspace.js[140-149]
src/observability/dashboard/ui/styles.js[43-48]

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 browser copy-audit claims to extract *visible/painted* text, but it currently walks all text nodes under the page root and does not exclude text inside `[hidden]`/`display:none` subtrees. This can cause false positives (duplicates in non-visible panels) and CI flakiness as hidden UI grows.

### Issue Context
The dashboard keeps tab panels in the DOM and toggles `panel.hidden` for non-active sections; CSS enforces `[hidden] { display:none !important; }`, so those nodes are not painted but are still present in the DOM.

### Fix Focus Areas
- tests/browser/dashboard-copy-audit-502.test.js[60-72]
- src/observability/dashboard/ui/pages/run-workspace.js[140-149]
- src/observability/dashboard/ui/styles.js[43-48]

### Implementation notes
In `page.evaluate`, skip a text node if its parent element is not visible, e.g.:
- `if (el.closest('[hidden]')) continue;`
- `const style = getComputedStyle(el); if (style.display==='none' || style.visibility==='hidden') continue;`
- optionally require geometry/paint: `el.offsetParent !== null` (with care for `position: fixed`) or `el.getClientRects().length > 0`.
This should ensure the test matches its “visible text” intent.

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



Informational

2. Overbroad slash dynamic filter 🐞 Bug ⚙ Maintainability
Description
The browser copy-audit skips any text containing '/', which excludes real user-facing copy like
“Proof / risks” and reduces the audit’s ability to catch duplicate-copy regressions for those
strings. This is a silent coverage gap: the suite will pass while missing duplicates it intends to
detect.
Code

tests/browser/dashboard-copy-audit-502.test.js[R50-55]

+const DYNAMIC_VALUE_PATTERNS = [
+  /\//,                                     // file paths, e.g. .rstack/runs/<id>/tasks.json
+  /^[A-Z][a-z]{2} \d{2}, \d{4},.*[AP]M/,     // rendered date/time, e.g. "Jul 01, 2026, 09:30:00 AM GMT+5:30"
+  /project-[0-9a-f]{6,}/,                    // hashed project id derived from the (random) temp root
+  /rstack-browser-[a-z0-9-]+-[A-Za-z0-9]{6}\b/, // mkdtemp-suffixed temp dir name embedded in a longer string
+];
Relevance

●● Moderate

No close historical review suggestions found about overbroad “skip any string with /” dynamic
filters in copy-audit tests.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The dynamic filter explicitly excludes all strings containing '/', but the UI contains legitimate
copy with slashes, so those strings will never be checked for duplicates by this suite.

tests/browser/dashboard-copy-audit-502.test.js[50-58]
src/observability/dashboard/ui/pages/run-workspace.js[78-85]
src/observability/dashboard/ui/pages/traceability.js[14-16]

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 browser copy-audit treats any string containing `/` as a dynamic value and excludes it. Many legitimate UI labels include slashes, so this reduces coverage and can let duplicate-copy regressions slip by.

### Issue Context
The intent is to exclude *file paths* and other data-like values, not general punctuation in authored prose.

### Fix Focus Areas
- tests/browser/dashboard-copy-audit-502.test.js[50-58]
- src/observability/dashboard/ui/pages/run-workspace.js[78-85]
- src/observability/dashboard/ui/pages/traceability.js[14-16]

### Implementation notes
Replace `/\//` with a more specific path detector, for example:
- strings that start with `/` or `./` or contain `/.rstack/`
- strings that look like a filesystem path segment pattern: `/(^|\s)(\.?\/|\/)[^\s]+\/[^\s]+/`
- or match known extensions when combined with slashes (e.g. `\.json`, `\.md`, `\.yaml`).
Goal: keep excluding real paths while still analyzing authored copy that happens to include slashes.

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


3. Diagnostic test asserts ok(true) 📜 Skill insight ▣ Testability
Description
dashboard-copy-audit-502.test.js includes a non-assertive assert.ok(true, ...) that always
passes, so it doesn't verify any actual behavior. This weakens the test suite and violates the
requirement that assertions test real outcomes rather than mere truthiness/existence.
Code

tests/browser/dashboard-copy-audit-502.test.js[R135-136]

+  t.diagnostic(`near-duplicate text across ${crossPage.length} block(s): ${JSON.stringify(crossPage.slice(0, 20))}`);
+  assert.ok(true, 'this check is diagnostic — it always passes, see the reported near-duplicate list above');
Relevance

● Weak

No-op assertion assert.ok(true, ...) replacement was explicitly suggested and rejected in PR #520.

PR-#520

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1399609 forbids assertions that only check truthiness/existence instead of
behavior. The added test ends with assert.ok(true, ...), which is tautological and therefore does
not verify any functional outcome.

tests/browser/dashboard-copy-audit-502.test.js[135-136]
Skill: qa-testing

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 test `#502: near-duplicate text across destinations is reported, not gated` ends with `assert.ok(true, ...)`, which always passes and does not validate behavior.

## Issue Context
This is intended to be diagnostic-only, but it still must include assertions that verify the code actually performed the intended work (e.g., visited all destinations and extracted text blocks), rather than a guaranteed-pass assertion.

## Fix Focus Areas
- tests/browser/dashboard-copy-audit-502.test.js[112-137]

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


4. Missing regression attribution block 📜 Skill insight ⚙ Maintainability
Description
This new regression/invariant browser test file is missing the required attribution fields (issue
ID, what broke, found date, and QA report path), reducing traceability and auditability of why the
test exists and where it came from.
Code

tests/browser/dashboard-copy-audit-502.test.js[R1-18]

+/**
+ * #502 layer 3: browser copy-audit sweep — extends the #96 browser-
+ * regression job. Extracts visible text blocks from the REAL painted DOM
+ * (a real Chromium via playwright-core, the same harness #96 uses) for
+ * every visible destination and:
+ *   - FAILS on an exact-duplicate text block within ONE page (the class of
+ *     bug behind #493 — a headline literally echoing another block's text),
+ *     unless the pair is in the documented allowlist.
+ *   - Reports (does not fail) near-duplicate blocks ACROSS pages — some
+ *     repetition there is by design (nav labels, chips, the deliberate
+ *     one-source-many-surfaces records like Action Inbox mirroring Needs
+ *     Attention), so this is diagnostic, not a gate.
+ *
+ * Isolated under tests/browser/ like every other suite in this directory —
+ * run with `npm run test:browser`; skips cleanly with no browser installed.
+ *
+ * owner: RStack developed by Richardson Gunde
+ */
Relevance

● Weak

Similar “add regression attribution metadata (found date/QA path)” suggestions repeatedly rejected
in PRs #508/#509/#524.

PR-#508
PR-#509
PR-#524

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1400067 requires regression tests to include attribution metadata (issue ID, what
broke, found date, QA report path). The newly added test headers reference #502 (and include
descriptive/owner context about the sweep/invariants and the real bug it found), but they do not
include a found date or any QA report reference/path and are not structured as a complete
attribution block, demonstrating the required fields are missing.

tests/browser/dashboard-copy-audit-502.test.js[1-18]
tests/hub-copy-lint-502.test.js[1-20]
tests/hub-projection-invariants-502.test.js[1-13]
Skill: qa-testing

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 regression/invariant test file headers do not include all required attribution fields (issue ID, description of what broke, date found, and QA report path), which weakens auditability and the ability to trace the tests back to the originating incident and QA evidence.

## Issue Context
PR Compliance ID 1400067 requires regression tests to carry an attribution comment block so future maintainers can understand provenance, link the test to the original issue/incident, and locate supporting QA report evidence.

## Fix Focus Areas
- tests/browser/dashboard-copy-audit-502.test.js[1-18]
- tests/hub-copy-lint-502.test.js[1-20]
- tests/hub-projection-invariants-502.test.js[1-13]

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


View more (1)
5. slice(-12) magic number 📜 Skill insight ⚙ Maintainability
Description
tests/hub-projection-invariants-502.test.js introduces literal numeric values (>= 4,
slice(-12)) in test logic instead of named constants, making the intent harder to maintain. This
violates the requirement to replace magic numbers with descriptive constants.
Code

tests/hub-projection-invariants-502.test.js[R55-56]

+  assert.ok(runScoped.length >= 4, `expected every run-scoped alert type to fire (cost, failure_rate, guardrail_rate, stalled_run) — got ${runScoped.map((a) => a.type).join(', ')}`);
+  const mangledSuffix = LONG_RUN_ID.slice(-12);
Relevance

● Weak

Magic-number-to-constant suggestions in tests were rejected (e.g., PRs #531 and #534).

PR-#531
PR-#534

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1400588 requires replacing literal numeric values with named constants. The added
test logic uses runScoped.length >= 4 and LONG_RUN_ID.slice(-12) directly rather than defining
descriptive constants for these invariants.

tests/hub-projection-invariants-502.test.js[55-56]
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 test uses magic numbers (`4` and `12`) directly in logic.

## Issue Context
This repo’s compliance rule requires literal numeric values used in conditions/calculations to be replaced with named constants for clarity and maintainability.

## Fix Focus Areas
- tests/hub-projection-invariants-502.test.js[55-56]

ⓘ 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 +60 to +71
async function extractTextBlocks(page, pageId) {
return page.evaluate((id) => {
const root = document.getElementById(`page-${id}`);
if (!root) return [];
const blocks = [];
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
let node;
while ((node = walker.nextNode())) {
const text = node.textContent.replace(/\s+/g, ' ').trim();
if (text) blocks.push(text);
}
return blocks;

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. Hidden text counted visible 🐞 Bug ☼ Reliability

extractTextBlocks() collects every text node under #page-${id} without checking whether the node
is actually visible, so hidden tab panels (e.g., run-workspace) can contribute text and trigger
false duplicate failures or flakes. This contradicts the test’s stated intent (“painted DOM”) and
will become brittle as hidden panels gain more copy.
Agent Prompt
### Issue description
The browser copy-audit claims to extract *visible/painted* text, but it currently walks all text nodes under the page root and does not exclude text inside `[hidden]`/`display:none` subtrees. This can cause false positives (duplicates in non-visible panels) and CI flakiness as hidden UI grows.

### Issue Context
The dashboard keeps tab panels in the DOM and toggles `panel.hidden` for non-active sections; CSS enforces `[hidden] { display:none !important; }`, so those nodes are not painted but are still present in the DOM.

### Fix Focus Areas
- tests/browser/dashboard-copy-audit-502.test.js[60-72]
- src/observability/dashboard/ui/pages/run-workspace.js[140-149]
- src/observability/dashboard/ui/styles.js[43-48]

### Implementation notes
In `page.evaluate`, skip a text node if its parent element is not visible, e.g.:
- `if (el.closest('[hidden]')) continue;`
- `const style = getComputedStyle(el); if (style.display==='none' || style.visibility==='hidden') continue;`
- optionally require geometry/paint: `el.offsetParent !== null` (with care for `position: fixed`) or `el.getClientRects().length > 0`.
This should ensure the test matches its “visible text” intent.

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

Comment on lines +50 to +55
const DYNAMIC_VALUE_PATTERNS = [
/\//, // file paths, e.g. .rstack/runs/<id>/tasks.json
/^[A-Z][a-z]{2} \d{2}, \d{4},.*[AP]M/, // rendered date/time, e.g. "Jul 01, 2026, 09:30:00 AM GMT+5:30"
/project-[0-9a-f]{6,}/, // hashed project id derived from the (random) temp root
/rstack-browser-[a-z0-9-]+-[A-Za-z0-9]{6}\b/, // mkdtemp-suffixed temp dir name embedded in a longer string
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

5. Overbroad slash dynamic filter 🐞 Bug ⚙ Maintainability

The browser copy-audit skips any text containing '/', which excludes real user-facing copy like
“Proof / risks” and reduces the audit’s ability to catch duplicate-copy regressions for those
strings. This is a silent coverage gap: the suite will pass while missing duplicates it intends to
detect.
Agent Prompt
### Issue description
The browser copy-audit treats any string containing `/` as a dynamic value and excludes it. Many legitimate UI labels include slashes, so this reduces coverage and can let duplicate-copy regressions slip by.

### Issue Context
The intent is to exclude *file paths* and other data-like values, not general punctuation in authored prose.

### Fix Focus Areas
- tests/browser/dashboard-copy-audit-502.test.js[50-58]
- src/observability/dashboard/ui/pages/run-workspace.js[78-85]
- src/observability/dashboard/ui/pages/traceability.js[14-16]

### Implementation notes
Replace `/\//` with a more specific path detector, for example:
- strings that start with `/` or `./` or contain `/.rstack/`
- strings that look like a filesystem path segment pattern: `/(^|\s)(\.?\/|\/)[^\s]+\/[^\s]+/`
- or match known extensions when combined with slashes (e.g. `\.json`, `\.md`, `\.yaml`).
Goal: keep excluding real paths while still analyzing authored copy that happens to include slashes.

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

@richard-devbot
richard-devbot merged commit 4ec77a8 into main Aug 1, 2026
10 checks passed
@richard-devbot
richard-devbot deleted the copy-audit-tooling-502 branch August 1, 2026 07:04
richard-devbot pushed a commit that referenced this pull request Aug 1, 2026
…ipped (PRs #535 #536 #538)

Co-Authored-By: Claude Sonnet 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.

1 participant