Skip to content

feat(uve): Page Scanner — a11y and GEO report tools with private URL handling#35212

Merged
wezell merged 53 commits intouve-experimentfrom
a11y-geo-report
Apr 6, 2026
Merged

feat(uve): Page Scanner — a11y and GEO report tools with private URL handling#35212
wezell merged 53 commits intouve-experimentfrom
a11y-geo-report

Conversation

@fmontes
Copy link
Copy Markdown
Member

@fmontes fmontes commented Apr 4, 2026

What

Adds two new Page Scanner tools to the Universal Visual Editor: an Accessibility (a11y) report and a GEO routing report, accessible from the Page Tools panel.

Why

Editors working in UVE need actionable feedback on WCAG compliance and geographic content performance without leaving the editor. The scanner calls an external API, so the feature needs to handle cases where the page URL is not publicly reachable.

Key decisions

  • Private URL detection: local/private addresses (localhost, 127.0.0.1, RFC-1918 ranges, .local) are detected client-side before any API call, immediately surfacing a consent gate and cloudflared tunnel setup guide — covers both local dotCMS instances and local headless webapps connected via UVE.
  • Consent gate: users must explicitly acknowledge the security implications of opening a tunnel before instructions are shown; resets on dialog close.
  • State model: uses signalState with a status: 'idle' | 'pending' | 'done' enum instead of boolean flags, per the Kent C. Dodds pattern.
  • Loading animation: dedicated dot-page-scan-loading component with an animated browser illustration and scan line — shared between both report types.
  • Chip styles decoupled from data models: color/style decisions live in the template, not the API response models.
  • Tailwind-only: SCSS dropped from page-tools and scanner components (except ::ng-deep dialog overrides).

Scope

  • New components: dot-page-scanner-report, dot-page-scanner-a11y-report, dot-page-scanner-geo-report, dot-page-scan-loading
  • New service: dot-page-scanner.service
  • i18n keys added to Language.properties
  • No backend Java changes

fmontes and others added 30 commits March 30, 2026 15:13
- Add DOT_PAGE_SCANNER_API_URL and DOT_PAGE_SCANNER_API_AUTH_TOKEN env vars with whitelist exemption for token key
- Add FEATURE_FLAG_PAGE_SCANNER boolean feature flag
- Add PageScannerResource proxy endpoints POST /api/v1/page-scanner/a11y/check and /geo/check with short-lived JWT injection and 503 guard when env vars are missing
- Add DotPageScannerReportComponent modal (90vh p-dialog) with skeleton loading, a11y accordion grouped by violation code, and GEO report with doughnut chart + category breakdown + top issues
- Refactor dot-page-tools-seo panel: add prominent Accessibility/GEO tiles at top (gated by flag), clamp existing tool descriptions to 2 lines
- Wire up in DotEmaShellComponent with feature flag guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tokens

Replace Tailwind bg-* classes on styleClass with [style] bindings overriding
--p-chip-background and --p-chip-color design tokens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ervice URL

- Simplified HTML structure in dot-page-scanner-report.component.html for better readability.
- Updated CLOUDFLARE_BASE URL in dot-page-scanner.service.ts to a new endpoint.
- Added new i18n message keys for GEO score in Language.properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…child components

- Extract DotPageScannerA11yReportComponent and DotPageScannerGeoReportComponent
- Move shared types to models.ts and CHIP_STYLES to chip-styles.ts
- Parent keeps dialog shell, loading/error state and HTTP orchestration
- Fix chart colors: Chart.js requires hex values, not CSS variables
- Add GEO score heading and description i18n keys

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all getXxx() template calls with computed() signals and pre-computed
data properties to avoid re-evaluation on every change detection cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…anner templates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ynamically in template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…to template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ture

- Changed line height in styles.scss to 1.4rem for better readability.
- Updated CLOUDFLARE_BASE URL in dot-page-scanner.service.ts.
- Added 'message' field to A11yGroup interface in models.ts for improved data handling.
- Refactored dot-page-scanner-a11y-report.component.html for better layout and accessibility.
- Enhanced dot-page-scanner-a11y-report.component.ts to include message descriptions in the report.
…blocks for element/selector

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…canner service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… enum in page scanner report

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nent, remove tags from model

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… scanner button design

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l instructions

Instead of sending localhost/private IPs to the scanner API and getting a 400,
detect private URLs before making the HTTP call and immediately show the tunnel
setup UI. Also adds the private URL error UI with 3-step cloudflared instructions
and i18n keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tions

Show a warning screen with two paragraphs (factual + safety note) and an
outlined accept button before revealing the tunnel setup steps. Resets
consent on dialog close. Also splits the consent message into two i18n keys
for clarity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…omponent

Add dot-page-scan-loading with a browser illustration and a scan line that
animates up and down in a loop, shared between a11y and geo reports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iption

Consent message now clearly mentions both dotCMS and headless apps in one
short sentence. Removed the duplicate description paragraph from the
instructions screen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fmontes and others added 14 commits April 4, 2026 17:37
…Link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove hardcoded default for DOT_PAGE_SCANNER_API_AUTH_TOKEN so
unconfigured installs correctly return 503. Remove the token from
ConfigurationResource whitelist and drop BLACKLIST_EXEMPTIONS to
prevent token exposure via /api/v1/configuration/config.
API URL remains in the whitelist as it is safe to expose.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import DotMessageService from @dotcms/data-access (not @dotcms/ui)
- Store takeUntilDestroyed() as field to satisfy injection context requirement
- Type scan$ as Observable union to resolve subscribe overload ambiguity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use standard dotCMS ResponseEntityView<ErrorEntity> for all error
responses instead of plain strings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detect PAGE_SCANNER_NOT_CONFIGURED error code from the 503 response
and show a dedicated UI telling the user to contact their administrator.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove client-side private URL pre-check so the not-configured error
takes priority. Private URL detection now relies solely on the server
error response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Map external API auth errors to 502 BAD_GATEWAY before returning to
the browser. A raw 401 from the upstream service would be intercepted
by the Angular HTTP interceptor and log the user out of dotCMS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detect PAGE_SCANNER_AUTH_FAILED error code and show a dedicated UI
with key_off icon telling the user credentials are invalid and to
contact their administrator.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace repeated icon+title+description divs with a reusable
DotPageScannerMessageComponent that supports ng-content for
optional extra content like buttons and warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…plate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines 5 to 21
margin-right: 2.25rem;
}
}

.p-dialog {
.p-dialog-header {
padding: spacing.$spacing-4;
border-bottom: 1px solid colors.$color-palette-gray-300;
padding: 1rem;
border-bottom: 1px solid var(--p-gray-300);

.p-dialog-header-icon {
opacity: 0;
}
}

.p-dialog-content {
padding: 0 spacing.$spacing-3;
padding: 0 0.75rem;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use vars if possible

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why did we removed tests?

…timization

- Button title: 'GEO Routing' -> 'Generative Engine Optimization'
- Button subtitle: 'Analyze geographic performance' -> 'Optimize content for AI-powered search engines'
- Dialog title: 'GEO Report' -> 'Generative Engine Optimization (GEO) Report'
- Score description: updated to name specific AI engines (Google AI Overviews, Perplexity, ChatGPT)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
long ttl = page.getCacheTTL() > 0 ? page.getCacheTTL() * 1000 : 60 * 60 * 24 * 7 * 1000; // 1 week

Logger.debug(this.getClass(), () -> "PageCache Put: ttl:" + ttl + " key:" + cacheKey);
Logger.info(this.getClass(), () -> "PageCache Put: ttl:" + ttl + " key:" + cacheKey);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changing the logging level here can generate too much noise. Do you really need this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah, this is from a bad merge - I think I just turned this to debug

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

revert this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not ours — this change came in via a merge, we did not modify StaticPageCacheImpl.java.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fmontes and others added 2 commits April 6, 2026 15:06
Restored files that were dropped during a merge conflict resolution:
- Task260403SetLz4CompressionOnTextColumns.java
- Task260403SetPermissionReferenceUnlogged.java
- FiltersTest.java (restored shouldResolvePageWithTrailingSlash test)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@wezell wezell left a comment

Choose a reason for hiding this comment

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

This is a bad merge, you are overwriting what was just merged into main, specifically:

#35187
#35151

long ttl = page.getCacheTTL() > 0 ? page.getCacheTTL() * 1000 : 60 * 60 * 24 * 7 * 1000; // 1 week

Logger.debug(this.getClass(), () -> "PageCache Put: ttl:" + ttl + " key:" + cacheKey);
Logger.info(this.getClass(), () -> "PageCache Put: ttl:" + ttl + " key:" + cacheKey);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah, this is from a bad merge - I think I just turned this to debug

long ttl = page.getCacheTTL() > 0 ? page.getCacheTTL() * 1000 : 60 * 60 * 24 * 7 * 1000; // 1 week

Logger.debug(this.getClass(), () -> "PageCache Put: ttl:" + ttl + " key:" + cacheKey);
Logger.info(this.getClass(), () -> "PageCache Put: ttl:" + ttl + " key:" + cacheKey);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

revert this

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@fmontes
Copy link
Copy Markdown
Member Author

fmontes commented Apr 6, 2026

@wezell all bad merged files reverted, good now.

fixed: 18233ac

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
final HttpServletRequest request,
final HttpServletResponse response,
final Map<String, Object> body,
final String checkType) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

checktype should be an enum I think

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — added CheckType enum with a11y and geo values.

public Response a11yCheck(
@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
final Map<String, Object> body) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Probably want a typed Form object here? Back at you Mr. Freddy Typescript!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — replaced Map<String, Object> with typed PageScanCheckForm DTO.

Replace string checkType param with CheckType enum (a11y/geo) and
replace Map<String, Object> request body with typed PageScanCheckForm.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@wezell wezell left a comment

Choose a reason for hiding this comment

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

Let's go!

@wezell wezell merged commit 2c49161 into uve-experiment Apr 6, 2026
14 checks passed
@wezell wezell deleted the a11y-geo-report branch April 6, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code Area : Documentation PR changes documentation files Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants