feat: add experience-auditor showcase example [EXT-7476]#11010
feat: add experience-auditor showcase example [EXT-7476]#11010Jared Jolton (jjolton-contentful) wants to merge 17 commits into
Conversation
⚔️ The Gauntlet —
|
| 🛑 Blockers | 0 |
| 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 onChange→audit(), 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>
…perience-auditor [EXT-7476]
…solution [EXT-7476]
301da2a to
3d005cb
Compare
EXT-7476 · sibling of EXT-7365 under epic EXT-6925
Summary
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 vianpx create-contentful-app --example experience-auditor.What it demonstrates
getRootNodes()→getProperties(), re-running onsdk.exo.experience.onChange(). Five rules ship: missing alt text, empty required headings, empty SEO metadata, broken entry bindings (resolved through the host'sresolveEntryBinding), and a cross-node heading-order check that flags skipped levels (e.g. H2 → H4).selection.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.getNode().setContentProperty(), permission-checked withsdk.access.can()and confirmed viasdk.notifier. A suggested value is never written silently.experience.publish()is blocked while any error-level finding remains.Design
Audit rules are pure functions over an SDK-independent
CollectedNodeshape (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 insrc/audit/keys.ts; suggested-fix derivation is a separate pure module (src/audit/fixes.ts). Adding a rule = dropping anotherAuditRuleintoAUDIT_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-memorysdk.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
ExperienceEditorToolbarAppSDKfrom@contentful/app-sdk@4.58.0. Follows repo conventions (React + TS + Vite + Vitest + Forma 36), mirroringexamples/typescript.Verification status
tsc --noEmitclean against the published 4.58.0 types.vite buildclean.sdk.exois still rolling out (EXT-7363/EXT-7364). README states this. API shapes match the published types exactly.Test plan
npm install && npm run buildinexamples/experience-auditorsucceedsnpm run test:cipassesnpx create-contentful-app --example experience-auditorscaffolds a working project (after merge tomaster)experience-toolbarlocation, 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 resolvedGenerated with Claude Code