-
Notifications
You must be signed in to change notification settings - Fork 2
A2UI v0.1 + Fordham Hill tenant (spec + 7 components + compose tool + conformance + tenant page) #2132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
A2UI v0.1 + Fordham Hill tenant (spec + 7 components + compose tool + conformance + tenant page) #2132
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
6fdfbc8
docs(website): baseline + brand asset family (playwright + axe-core +…
hunnibear ee88268
docs(agnote): CLAIM WEBSITE_AS_AGENT_CANVAS lane (DARKXSIDE leadershi…
hunnibear 9a99d12
docs(contracts): A2UI v0.1 spec - HTML5 Web Components, push+pull C/s…
hunnibear 241e6a6
feat(a2ui): v0.1 first slice - spec + 3 components + compose tool + c…
hunnibear af75ea0
feat(a2ui): complete v0.1 registry + Fordham Hill tenant page + tenan…
hunnibear 449d918
fix(a2ui): close v0.1 renderer XSS surfaces (peer review #2132)
hunnibear bd09b16
fix(a2ui): self-host Fordham tenant media (no-CDN rule, peer review #…
hunnibear 0cccd47
fix(a2ui): correctness nits from peer review #2132
hunnibear fd304ba
docs(a2ui): PR #2132 peer-review LEARNINGS (four-bucket, per REVIEW_S…
hunnibear bb49bcb
Merge remote-tracking branch 'origin/main' into HEAD
hunnibear File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <title>A2UI v0.1 — Conformance Test</title> | ||
| <style> | ||
| :root { | ||
| --pm-accent: #7C3AED; | ||
| --pm-accent-soft: #A78BFA; | ||
| --pm-accent-strong: #5B21B6; | ||
| --pm-bg: #0b0b10; | ||
| --pm-bg-elevated: #13131a; | ||
| --pm-fg: #FFFFFF; | ||
| --pm-fg-muted: #9ca3af; | ||
| --pm-border: rgba(255, 255, 255, 0.08); | ||
| --pm-radius: 12px; | ||
| --pm-radius-sm: 6px; | ||
| --pm-font-display: 'Orbitron', system-ui; | ||
| --pm-font-body: system-ui, -apple-system, sans-serif; | ||
| --pm-spacing-unit: 8px; | ||
| --pm-motion-fast: 120ms cubic-bezier(0.2, 0, 0, 1); | ||
| --pm-motion-slow: 400ms cubic-bezier(0.2, 0, 0, 1); | ||
| } | ||
| body { | ||
| margin: 0; | ||
| padding: 24px; | ||
| background: var(--pm-bg); | ||
| color: var(--pm-fg); | ||
| font-family: var(--pm-font-body); | ||
| } | ||
| h1 { | ||
| font-family: var(--pm-font-display); | ||
| font-size: 18px; | ||
| margin: 0 0 16px; | ||
| } | ||
| pre { | ||
| background: var(--pm-bg-elevated); | ||
| border: 1px solid var(--pm-border); | ||
| border-radius: var(--pm-radius-sm); | ||
| padding: 12px; | ||
| font-size: 12px; | ||
| overflow: auto; | ||
| color: var(--pm-fg); | ||
| } | ||
| .status { font-weight: 600; } | ||
| .status.pass { color: #10b981; } | ||
| .status.fail { color: #ef4444; } | ||
| .status.warn { color: #f59e0b; } | ||
| section { margin-bottom: 24px; } | ||
| h2 { | ||
| font-size: 14px; | ||
| text-transform: uppercase; | ||
| letter-spacing: 0.05em; | ||
| color: var(--pm-fg-muted); | ||
| margin: 0 0 8px; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>A2UI v0.1 — Conformance Test</h1> | ||
|
|
||
| <section id="registry"> | ||
| <h2>Component registration</h2> | ||
| <pre id="registry-out">Running...</pre> | ||
| </section> | ||
|
|
||
| <section id="tokens"> | ||
| <h2>Token check (--pm-* present)</h2> | ||
| <pre id="tokens-out">Running...</pre> | ||
| </section> | ||
|
|
||
| <section id="shadow"> | ||
| <h2>Shadow DOM encapsulation</h2> | ||
| <pre id="shadow-out">Running...</pre> | ||
| </section> | ||
|
|
||
| <section id="a11y"> | ||
| <h2>Axe-core 4.x (WCAG 2 AA)</h2> | ||
| <pre id="a11y-out">Running...</pre> | ||
| </section> | ||
|
|
||
| <!-- Test surface: one of each v0.1 shipped component --> | ||
| <section id="test-surface" style="display:none"> | ||
| <pm-space-agent-card agent-name="Test Agent" agent-role="test" presence="live"></pm-space-agent-card> | ||
| <pm-project-card title="Test Project" description="Test desc" status="live"></pm-project-card> | ||
| <pm-metric-tile label="Test metric" value="42" trend="up"></pm-metric-tile> | ||
| <pm-timeline events='[{"ts":"2026-07-15T10:00:00Z","title":"Event 1","body":"Test body","icon":"◆"}]'></pm-timeline> | ||
| <pm-voice-clip src="data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA=" title="Test clip" duration="0:01"></pm-voice-clip> | ||
| <pm-image src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiPjwvc3ZnPg==" alt="Test" caption="Test caption"></pm-image> | ||
| <pm-quote-block quote="Test quote" attribution="Test" attribution-role="Tester"></pm-quote-block> | ||
| </section> | ||
|
|
||
| <!-- Inject components --> | ||
| <script type="module"> | ||
| // Import all v0.1 components (7 total) | ||
| import '../web-components/pm-space-agent-card/pm-space-agent-card.js'; | ||
| import '../web-components/pm-project-card/pm-project-card.js'; | ||
| import '../web-components/pm-metric-tile/pm-metric-tile.js'; | ||
| import '../web-components/pm-timeline/pm-timeline.js'; | ||
| import '../web-components/pm-voice-clip/pm-voice-clip.js'; | ||
| import '../web-components/pm-image/pm-image.js'; | ||
| import '../web-components/pm-quote-block/pm-quote-block.js'; | ||
|
|
||
| const out = (id, text, klass) => { | ||
| const el = document.getElementById(id); | ||
| el.textContent = text; | ||
| el.className = klass || ''; | ||
| }; | ||
|
|
||
| // 1. Registry check | ||
| const expected = [ | ||
| 'pm-space-agent-card', | ||
| 'pm-project-card', | ||
| 'pm-metric-tile', | ||
| 'pm-timeline', | ||
| 'pm-voice-clip', | ||
| 'pm-image', | ||
| 'pm-quote-block', | ||
| ]; | ||
| const registered = expected.filter((n) => customElements.get(n)); | ||
| if (registered.length === expected.length) { | ||
| out('registry-out', `✓ All ${expected.length}/7 v0.1 components registered:\n - ${registered.join('\n - ')}`, 'pass'); | ||
| } else { | ||
| const missing = expected.filter((n) => !customElements.get(n)); | ||
| out('registry-out', `✗ Missing: ${missing.join(', ')}\n Registered: ${registered.join(', ')}`, 'fail'); | ||
| } | ||
|
|
||
| // 2. Token check — all components read --pm-* tokens | ||
| const components = document.querySelectorAll('#test-surface > *'); | ||
| document.getElementById('test-surface').style.display = 'block'; | ||
| const tokenReport = []; | ||
| components.forEach((comp) => { | ||
| // Get the CSS text from the first <style> in the shadow root. | ||
| // We read the raw textContent (works for HTMLStyleElement) and fall | ||
| // back to adoptedStyleSheets[0].cssText for the CSSStyleSheet case. | ||
| const styleEl = comp.shadowRoot?.querySelector('style'); | ||
| let cssText = ''; | ||
| if (styleEl) { | ||
| cssText = styleEl.textContent || ''; | ||
| } else { | ||
| const adopted = comp.shadowRoot?.adoptedStyleSheets?.[0]; | ||
| if (adopted) cssText = Array.from(adopted.cssRules || []).map((r) => r.cssText).join('\n'); | ||
| } | ||
| // Check that --pm-* tokens are used and no hardcoded hex colors leak in the default styles | ||
| const usesTokens = /var\(--pm-/.test(cssText); | ||
| const hasHardcodedHex = /#[0-9a-fA-F]{3,8}/.test(cssText); | ||
| // --pm-bg, --pm-fg etc. all need to be referenced | ||
| const tokenCount = (cssText.match(/var\(--pm-/g) || []).length; | ||
| tokenReport.push(` ${comp.tagName.toLowerCase()}: ${usesTokens ? '✓' : '✗'} uses --pm-* tokens (${tokenCount} refs)${hasHardcodedHex ? ' [HARDCODED HEX!]' : ''}`); | ||
| }); | ||
| const allUseTokens = tokenReport.every((r) => r.includes('✓')); | ||
| out('tokens-out', tokenReport.join('\n'), allUseTokens ? 'pass' : 'fail'); | ||
|
|
||
| // 3. Shadow DOM check | ||
| const shadowReport = []; | ||
| components.forEach((comp) => { | ||
| const hasShadow = !!comp.shadowRoot; | ||
| const mode = hasShadow && comp.shadowRoot.mode ? comp.shadowRoot.mode : 'none'; | ||
| shadowReport.push(` ${comp.tagName}: shadowRoot=${hasShadow ? 'yes' : 'no'} (mode=${mode})`); | ||
| }); | ||
| const allShadow = shadowReport.every((r) => r.includes('yes')); | ||
| out('shadow-out', shadowReport.join('\n'), allShadow ? 'pass' : 'fail'); | ||
|
|
||
| // 4. Axe-core a11y | ||
| const axeScript = document.createElement('script'); | ||
| axeScript.src = 'https://cdn.jsdelivr.net/npm/axe-core@4.10.2/axe.min.js'; | ||
| axeScript.onload = async () => { | ||
| try { | ||
| const result = await window.axe.run(document, { | ||
| runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21aa'] }, | ||
| }); | ||
| const violations = result.violations; | ||
| if (violations.length === 0) { | ||
| out('a11y-out', `✓ 0 violations\n ${result.passes.length} rules passed`, 'pass'); | ||
| } else { | ||
| const lines = violations.map((v) => | ||
| ` [${v.impact}] ${v.id} - ${v.help} (${v.nodes.length} nodes)` | ||
| ); | ||
| out('a11y-out', `✗ ${violations.length} violations:\n${lines.join('\n')}`, 'fail'); | ||
| } | ||
| } catch (e) { | ||
| out('a11y-out', `✗ axe-core error: ${e.message}`, 'fail'); | ||
| } | ||
| }; | ||
| document.body.appendChild(axeScript); | ||
| </script> | ||
| </body> | ||
| </html> | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Exclude
var()fallbacks from the hardcoded hex color check.The current regex flags legitimate hex colors provided as fallback values inside CSS custom properties (e.g.,
var(--pm-bg,#0b0b10)). This produces misleading[HARDCODED HEX!]warnings in the conformance output. Strippingvar()usages before matching the regex prevents these false positives.💚 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents