Skip to content

feat: add experience-auditor showcase example [EXT-7476]#11010

Open
Jared Jolton (jjolton-contentful) wants to merge 17 commits into
masterfrom
feat/experience-auditor-showcase
Open

feat: add experience-auditor showcase example [EXT-7476]#11010
Jared Jolton (jjolton-contentful) wants to merge 17 commits into
masterfrom
feat/experience-auditor-showcase

Conversation

@jjolton-contentful

@jjolton-contentful Jared Jolton (jjolton-contentful) commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

EXT-7476 · sibling of EXT-7365 under epic EXT-6925

Summary

  • Adds examples/experience-auditor — a polished, customer-facing showcase app for the ExO toolbar location that goes well beyond the minimal starter (PR feat: add experience-toolbar starter example [EXT-7365] #11009 / EXT-7365). Scaffoldable via npx create-contentful-app --example experience-auditor.
  • Experience Auditor runs inside the Experience Editor toolbar and continuously audits the experience for accessibility, SEO, and content-completeness issues — demonstrating the standout capability of this location: live, selection-aware tooling that reads and mutates the experience tree as the author works.

What it demonstrates

  • Live audit — walks the tree via getRootNodes()getProperties(), re-running on sdk.exo.experience.onChange(). Five rules ship: missing alt text, empty required headings, empty SEO metadata, broken entry bindings (resolved through the host's resolveEntryBinding), and a cross-node heading-order check that flags skipped levels (e.g. H2 → H4).
  • Scored dashboard — a 0–100 health score (rendered as a ring gauge) with findings grouped by severity into error / warning / info.
  • Locate on canvasselection.set() + selection.highlight(nodeId, { flash, scrollIntoView }) jumps to the offending component. Visual mode only; gracefully disabled when the host does not expose a selection surface, with an inline explanation rather than a dead button.
  • Two kinds of one-click fixdeterministic repairs apply immediately (trim stray alt-text whitespace, set a skipped heading to the sequential level); suggested repairs (e.g. an SEO meta value derived from the component's heading) pre-fill an editable field the author confirms before anything is written. Both write through getNode().setContentProperty(), permission-checked with sdk.access.can() and confirmed via sdk.notifier. A suggested value is never written silently.
  • Pre-publish gateexperience.publish() is blocked while any error-level finding remains.

Design

Audit rules are pure functions over an SDK-independent CollectedNode shape (src/audit/rules.ts), so they are fully unit-tested without a live SDK. SDK coupling is confined to a thin collector (src/audit/collect.ts), which resolves entry bindings, and a capability probe (src/audit/capabilities.ts), which detects what the host backs. Shared key-matching lives in src/audit/keys.ts; suggested-fix derivation is a separate pure module (src/audit/fixes.ts). Adding a rule = dropping another AuditRule into AUDIT_RULES (or, for an order-sensitive check, an evaluator the engine runs over the full node list).

A dev-only demo mode (npm start, then open /?demo) renders the panel against a seeded in-memory sdk.exo, so the audit → suggested-fix → re-score loop is clickable without a live host. The demo scaffolding is dynamically imported and stripped from production builds.

Typed with ExperienceEditorToolbarAppSDK from @contentful/app-sdk@4.58.0. Follows repo conventions (React + TS + Vite + Vitest + Forma 36), mirroring examples/typescript.

Verification status

  • tsc --noEmit clean against the published 4.58.0 types.
  • vite build clean.
  • 41/41 tests pass: rule/scoring, collector (including binding resolution and the partial-host fallback), the pure suggested-fix derivation, capability detection, and the toolbar integration paths (render-with-score, publish-gate, locate, form-mode disabling, deterministic fix-apply, suggested fix-apply, and the findings-list re-seed).
  • ⚠️ Not runtime-verified inside a live ExO editor — the host renderer serving sdk.exo is still rolling out (EXT-7363/EXT-7364). README states this. API shapes match the published types exactly.

Test plan

  • npm install && npm run build in examples/experience-auditor succeeds
  • npm run test:ci passes
  • npx create-contentful-app --example experience-auditor scaffolds a working project (after merge to master)
  • (When host renderer lands) install, register the experience-toolbar location, open an experience with a missing-alt-text image, confirm the finding appears, Locate highlights the node, the fix writes back, and publish unblocks once errors are resolved

Generated with Claude Code

@lewisjcs

Copy link
Copy Markdown
Contributor

⚔️ The Gauntlet — ⚠️ 0 Blockers · 6 advisory

🤖 AI-powered · your code, through the lanes

gauntlet verdict

🛑 Blockers 0
⚠️ Concerns 4
💡 Nits 2
🔒 Security clean ✓
🧪 Lanes code-quality · adversarial · doc-review (PR body) · security

Findings

Finding Location Status
⚠️ README says the fix path is tested, but no fix-apply test exists — only the locate, publish-gate, and form-mode toolbar tests do (handleFix write-back is untested). README.md:108 Open
⚠️ IMAGE_KEY_HINT matches non-image keys like assetId/iconName/logoText, so a component with such a field and no alt sibling emits a false error that also blocks the publish gate. src/audit/rules.ts:5 Open
⚠️ The PR body's "4 toolbar integration (locate, fix, publish-gate, form-mode)" lists a fix test that isn't among the four — the real fourth is render-with-score. PR body, Verification status Open
⚠️ onChange → audit() has no debounce; form-mode rapid edits fan out to N+1 SDK calls per burst. collect.ts documents its analogous simplification — this one doesn't. src/locations/ExperienceToolbar.tsx:88 Open
💡 metaTitle/seoTitle match both the heading and meta hints, so one empty field double-fires two findings (−5 score) with no cross-rule dedup. src/audit/rules.ts Open
💡 handleFix re-audits twice — setContentProperty fires onChangeaudit(), then handleFix also await audit(); the first traversal is discarded. src/locations/ExperienceToolbar.tsx Open

The Gauntlet clears this PR for merge — nothing here blocks. The architecture (pure rules over an SDK-independent shape + a thin collector) is genuinely clean, and tsc, the 19 tests, and vite build all verify locally against the published 4.58.0 types. Two themes worth a pass before merge: the fix path is described as tested in two places but isn't (README + PR body), and the alt-text rule's key-matching is broad enough to fire false errors on non-image fields. Both are the kind of thing that propagates when developers copy an example, so they're worth tightening even though none gates the merge.

🤖 Machine-readable findings (for agents)
{
  "tool": "gauntlet",
  "schema": "v1",
  "reviewed_ref": "0033e9dd6",
  "verdict": { "blockers": 0, "concerns": 4, "nits": 2, "security": "clean" },
  "findings": [
    { "severity": "concern", "location": "README.md:108", "lens": "adversarial-review / Blast Radius", "confidence": 98, "claim": "README says toolbar fix behavior is tested, but no fix-apply test exists; handleFix write-back path is uncovered.", "recommendation": "Add a fix-apply integration test or soften the README claim." },
    { "severity": "concern", "location": "src/audit/rules.ts:5", "lens": "adversarial-review / Hidden Assumptions", "confidence": 97, "claim": "IMAGE_KEY_HINT matches non-image keys (assetId, iconName, logoText); non-empty such field with no alt sibling emits a false error that blocks the publish gate.", "recommendation": "Tighten the matcher to the asset-reference value shape, or document the false-positive surface." },
    { "severity": "concern", "location": "PR body, Verification status", "lens": "doc-review / Accuracy of references", "confidence": 92, "claim": "Test-plan parenthetical names a 'fix' toolbar test that does not exist; real fourth test is render-with-score.", "recommendation": "Correct to (render-with-score, publish-gate, locate, form-mode disabling)." },
    { "severity": "concern", "location": "src/locations/ExperienceToolbar.tsx:88", "lens": "adversarial-review / Failure Scenarios", "confidence": 91, "claim": "onChange->audit() has no debounce; form-mode bursts fan out to N+1 SDK calls; production gap is undocumented unlike collect.ts.", "recommendation": "Debounce the re-audit or add a production-gap comment." },
    { "severity": "nit", "location": "src/audit/rules.ts", "lens": "adversarial-review / Hidden Assumptions", "confidence": 96, "claim": "metaTitle/seoTitle match both heading and meta hints; one empty field double-fires two findings (-5 score) with no cross-rule dedup.", "recommendation": "Dedup by (nodeId, propertyKey) or make the hints mutually exclusive." },
    { "severity": "nit", "location": "src/locations/ExperienceToolbar.tsx", "lens": "adversarial-review / Failure Scenarios", "confidence": 90, "claim": "handleFix triggers a redundant double-audit (onChange + explicit await audit()); first traversal discarded.", "recommendation": "Drop the explicit audit() and rely on onChange, or guard it." }
  ]
}

🎮 The Gauntlet · an AI review harness built by Josh C.S. Lewis · reviewed at 0033e9dd6. Everything but the 🛑 is advisory.

Add a polished, customer-facing example app for the ExO toolbar location
that goes beyond the minimal starter (EXT-7365). Experience Auditor runs
inside the Experience Editor toolbar and continuously audits the
experience for accessibility, SEO, and content-completeness issues.

It demonstrates the standout capability of the toolbar location: live,
selection-aware tooling that reads and mutates the experience tree. A
scored dashboard lists findings; clicking one locates the offending
component on the canvas (selection.set + selection.highlight), safe
deterministic fixes apply in place via setContentProperty (permission-
checked, with notifier feedback), and publish is gated on outstanding
errors.

Audit rules are pure functions over an SDK-independent node shape, fully
unit-tested without a live SDK; the SDK boundary is isolated in a thin
collector. Typed with ExperienceEditorToolbarAppSDK (app-sdk@4.58.0).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…476]

Address code review on the experience-auditor showcase:
- alt-text rule now requires an asset-shaped value, not just an image-ish
  key, so string fields (iconName, logoText, assetId) no longer emit false
  errors that block the publish gate
- heading rule excludes meta-ish keys so metaTitle/seoTitle no longer
  double-fire across the heading and SEO rules
- add a fix-apply integration test (setContentProperty + re-audit) so the
  documented fix path is actually covered
- add regression tests for the false-positive and double-fire cases
- document the onChange re-audit no-debounce simplification and why
  handleFix re-audits explicitly

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lewisjcs Josh Lewis (lewisjcs) force-pushed the feat/experience-auditor-showcase branch from 301da2a to 3d005cb Compare June 8, 2026 18:21
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.

2 participants