diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 4f704dcd13..2f982d533d 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -87,7 +87,7 @@ jobs:
run: cd backend && npm ci --include=dev
- name: Install frontend dependencies
- run: cd frontend && npm install --legacy-peer-deps
+ run: cd frontend && OPENUI_TELEMETRY_DISABLED=1 npm install --legacy-peer-deps
- name: Install Playwright browsers
run: npx playwright install chromium --with-deps
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a32c39cec4..15a14358e1 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -129,7 +129,7 @@ jobs:
frontend/package-lock.json
# Frontend typecheck follows shared imports into backend TypeScript.
- run: cd backend && npm ci --include=dev
- - run: cd frontend && npm install --legacy-peer-deps
+ - run: cd frontend && OPENUI_TELEMETRY_DISABLED=1 npm install --legacy-peer-deps
- run: cd frontend && npm run typecheck
- name: Run frontend tests (coverage on the nightly schedule only)
run: |
@@ -139,7 +139,7 @@ jobs:
else
npx jest --watchAll=false --forceExit
fi
- - run: cd frontend && node --test scripts/generate-seo-pages.test.mjs scripts/nginx-routing.test.mjs
+ - run: cd frontend && node --test scripts/generate-seo-pages.test.mjs scripts/nginx-routing.test.mjs scripts/ui-card-catalogue.test.mjs
tool-tests:
name: CLI, MCP and hosted runtime tests
diff --git a/commonly-mcp/__tests__/tools.test.mjs b/commonly-mcp/__tests__/tools.test.mjs
index 918f23a1b8..22a67c5e77 100644
--- a/commonly-mcp/__tests__/tools.test.mjs
+++ b/commonly-mcp/__tests__/tools.test.mjs
@@ -5,6 +5,7 @@
* (via the same _fetchImpl seam in client.js) and assert the wire shape.
*/
+import { readFileSync } from 'node:fs';
import { jest } from '@jest/globals';
import { buildTools } from '../src/tools.js';
import { HttpError } from '../src/client.js';
@@ -690,3 +691,10 @@ describe('per-event inbox checkpoint', () => {
expect(fetchSpy).toHaveBeenCalledTimes(1);
});
});
+
+test('post schema publishes the current catalogue tuple without a private manifest lookup', () => {
+ const revision = JSON.parse(readFileSync(new URL('../../backend/config/uiCardCatalogueRevision.json', import.meta.url), 'utf8'));
+ const description = byName.commonly_post_message.inputSchema.properties.payload.description;
+ for (const value of Object.values(revision)) expect(description).toContain(String(value));
+ expect(description).not.toContain('published catalogue revision manifest');
+});
diff --git a/docs/audits/task-498/README.md b/docs/audits/task-498/README.md
new file mode 100644
index 0000000000..df432dfd12
--- /dev/null
+++ b/docs/audits/task-498/README.md
@@ -0,0 +1,31 @@
+# TASK-498 renderer proof
+
+Artboard: TASK-495, expanded to text in PR #2675 comment 6395684963.
+
+The screenshots render the actual V2UICard and OpenUI 1.0.1 runtime against local fixtures. No production API, agent or grant was used. The preview is `frontend/design-system/preview/ui-card.html` compiled by Vite in production mode and served by its preview server. Browser API routes supplied the board task and the trace receipt. Both widths have seven cards, including malformed, expired and unsupported revision states.
+
+- 1440: no horizontal overflow, ordinary primary link 32px tall.
+- 390: no horizontal overflow, footer link 44px tall and full width; the three-column table becomes records.
+- No page errors or OpenUI inspector auto-mount at either width.
+- Static real-package render passes on the lockfile's React 18.3.1.
+
+`catalogue-1440.png` and `catalogue-390.png` capture the actual component specimens. Ego's seat wrapper denied access, so these were measured with an isolated headless Chrome profile using Playwright, without any signed-in browser session.
+
+The renderer accepts only the exact commonly.ui revision-1 ID/revision/hash tuple; missing or unsupported tuples retain the content with an unsupported-version note. Its hostname allowlist is exact: commonly.me, docs.commonly.me, github.com, HTTPS only, without credentials or custom ports. Other links are visible but unavailable. Legacy gig cards retain their narrower GitHub-only rule. Card source is never HTML and receives no tool provider.
+
+TaskStatus reads the current pod's board. RunTrace reads the pod-scoped TASK-499 endpoint from the server-stamped run ID; it uses only name, outcome and duration. Endpoint routing/provenance is supplied by #2686. No raw arguments or results enter the preview.
+
+The canonical grammar is owned by TASK-497 in `backend/config/uiCardCatalogue.json`. Run `node frontend/scripts/sync-ui-card-catalogue.mjs` to update the frontend copy; the byte-for-byte tests preserve positional prop order and the revision manifest.
+
+## Repeat the production check
+
+From the repository root with root and frontend dependencies installed:
+
+```sh
+OPENUI_TELEMETRY_DISABLED=1 npm ci --prefix frontend --legacy-peer-deps
+node scripts/check-ui-card-production.mjs
+```
+
+The check compiles the real component specimen with the production Vite configuration, checks emitted JavaScript for the devtools CDN/mount code, opens the production assets in isolated Playwright Chromium, records outgoing requests and measures both widths. Set `CHROME_EXECUTABLE` to use an existing Chrome binary. It does not call a live API. `production-proof.json` records zero jsDelivr/PostHog requests and page errors. This is local production-build evidence, not a deployed walk.
+
+Source parsing rejects dormant capabilities and bounds reference expansion before materialization (4,096 AST nodes across statements), plus a separate parsed-tree bound. Tests cover an exponential repeated-reference graph and its valid small control. The renderer has no A2UI interoperability claim.
diff --git a/docs/audits/task-498/catalogue-1440.png b/docs/audits/task-498/catalogue-1440.png
new file mode 100644
index 0000000000..4ffed14570
Binary files /dev/null and b/docs/audits/task-498/catalogue-1440.png differ
diff --git a/docs/audits/task-498/catalogue-390.png b/docs/audits/task-498/catalogue-390.png
new file mode 100644
index 0000000000..4b26fd6c7f
Binary files /dev/null and b/docs/audits/task-498/catalogue-390.png differ
diff --git a/docs/audits/task-498/production-proof.json b/docs/audits/task-498/production-proof.json
new file mode 100644
index 0000000000..bf27b9071f
--- /dev/null
+++ b/docs/audits/task-498/production-proof.json
@@ -0,0 +1,21 @@
+{
+ "mode": "production",
+ "api": "controlled fixtures; no deployed walk",
+ "measurements": [
+ {
+ "width": 1440,
+ "overflow": false,
+ "buttonHeight": 32,
+ "devtools": 0
+ },
+ {
+ "width": 390,
+ "overflow": false,
+ "buttonHeight": 44,
+ "devtools": 0
+ }
+ ],
+ "jsdelivrRequests": 0,
+ "posthogRequests": 0,
+ "pageErrors": []
+}
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index c2efbe8715..0cbee95f0d 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -4,6 +4,7 @@ WORKDIR /app
COPY package*.json ./
+ENV OPENUI_TELEMETRY_DISABLED=1
RUN npm install
COPY . .
diff --git a/frontend/design-system/preview/ui-card.html b/frontend/design-system/preview/ui-card.html
new file mode 100644
index 0000000000..09fe38502d
--- /dev/null
+++ b/frontend/design-system/preview/ui-card.html
@@ -0,0 +1 @@
+
diff --git a/frontend/design-system/preview/ui-card.tsx b/frontend/design-system/preview/ui-card.tsx
new file mode 100644
index 0000000000..7d07efc07c
--- /dev/null
+++ b/frontend/design-system/preview/ui-card.tsx
@@ -0,0 +1,20 @@
+import revision from '../../src/v2/cards/uiCardCatalogueRevision.json';
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import { MemoryRouter } from 'react-router-dom';
+import { SocketProvider } from '../../src/context/SocketContext';
+import { AuthContext } from '../../src/context/AuthContext';
+import V2UICard from '../../src/v2/cards/V2UICard';
+import '../../src/v2/v2.css';
+import '@fontsource/ibm-plex-sans/400.css';
+import '@fontsource/ibm-plex-sans/600.css';
+import '@fontsource/ibm-plex-mono/500.css';
+const sources = [
+ 'root = Card("Release assessment", [Text("The change preserves identity and existing grants."), Steps([Step("Read the patch", "done"), Step("Check the release", "active", "CI is running"), Step("Publish", "pending")]), DiffSummary([File("frontend/src/v2/cards/V2UICard.tsx", 84, 12), File("backend/config/uiCardCatalogue.json", 30, 2)]), Link("Open pull request", "https://github.com/Team-Commonly/commonly/pull/2675", "primary")], "4 minutes ago", "The catalogue uses the same Signal tokens as the shell.")',
+ 'root = Card("Project state", [TaskStatus("TASK-498", "Agent note: ready for review"), KeyValue([Pair("Repository", "Team-Commonly/commonly"), Pair("Tests", 71)]), Badge("Review pending", "pending"), List(["The component API is fixed", "The trace persists"], true)])',
+ 'root = Card("Verification", [Table([Col("Surface"), Col("Width", "number"), Col("Result")], [["Pod chat", 1440, "Ready"], ["Pod chat", 390, "Ready"]]), RunTrace(3)])',
+ 'root = Card("Links", [Text(["Source: ", Link("GitHub", "https://github.com/Team-Commonly/commonly", "text")]), Link("Unapproved host", "https://github.com.evil.test/", "button"), Badge("Done", "done")])',
+ 'root = Text(@Run("unsafe"))',
+];
+const auth = { token:null, currentUser:null, user:null, loading:false, error:null, isAuthenticated:false } as any;
+createRoot(document.getElementById('root')!).render(