open-design/apps/daemon/tests/plugins-genui-component.test.ts
Cursor Agent 6b114910e4
feat(plugins): od.genui.surfaces[].component manifest field (Phase 4)
Plan K3 / spec §10.3.5 alignment-roadmap row 2.

Plugin authors can now declare a per-surface React component path:

  od.genui.surfaces[*].component: {
    path:    string,           # required, no traversal segments
    export?: string,           # optional named export
    sandbox?: 'iframe' | 'react'
  }

The capability gate is wired in three places:

- packages/contracts: GenUISurfaceSpecSchema.component is a passthrough
  Zod object so the validator accepts the new field on round-trip.
- apps/daemon/src/plugins/trust.ts: 'genui:custom-component' joins the
  KNOWN_TOP_LEVEL_CAPABILITIES set so od plugin trust can grant it.
- apps/daemon/src/plugins/doctor.ts: doctorPlugin emits
  genui.component-capability when a surface ships a component without
  the matching capability declared, and genui.component-traversal when
  the path includes '..'.

The web GenUISurfaceRenderer (apps/web/src/components/GenUISurfaceRenderer.tsx)
keeps its built-in renderer for v1; loading the bundled component is
the next slice and depends on a sandbox wrapper that doesn't exist
yet (the spec §9.2 preview sandbox is the inspiration but
insufficient — components need React boundaries).

Daemon tests: 1490 → 1496 (+6 cases on plugins-genui-component:
schema accepts the new field shape, rejects empty path, capability
appears in the §5.3 vocabulary, doctor errors when capability is
missing, doctor passes when granted, doctor catches traversal).

Co-authored-by: Tom Huang <1043269994@qq.com>
2026-05-09 13:37:20 +00:00

139 lines
4.8 KiB
TypeScript

// Plan §3.K3 / spec §10.3.5 — od.genui.surfaces[].component manifest field.
//
// Two contracts:
// 1. The Zod schema in @open-design/contracts accepts the new
// `component: { path, export?, sandbox? }` field on a surface.
// 2. doctorPlugin() flags a surface that ships a component without
// the matching `genui:custom-component` capability, and rejects
// path-traversal segments.
// 3. validateCapabilityList accepts `genui:custom-component` as a
// first-class top-level capability.
import { describe, expect, it } from 'vitest';
import { GenUISurfaceSpecSchema } from '@open-design/contracts';
import { validateSafe } from '@open-design/plugin-runtime';
import { doctorPlugin } from '../src/plugins/doctor.js';
import { validateCapabilityList } from '../src/plugins/trust.js';
import { FIRST_PARTY_ATOMS, type AtomCatalogEntry } from '../src/plugins/atoms.js';
import type { InstalledPluginRecord, PluginManifest } from '@open-design/contracts';
const REGISTRY = {
skills: [],
designSystems: [],
craft: [],
atoms: FIRST_PARTY_ATOMS.map((a: AtomCatalogEntry) => ({ id: a.id, label: a.label })),
};
function pluginRecord(manifest: PluginManifest): InstalledPluginRecord {
return {
id: manifest.name,
title: manifest.title ?? manifest.name,
version: manifest.version,
sourceKind: 'local',
source: '/tmp/test',
pinnedRef: undefined,
sourceMarketplaceId: undefined,
trust: 'restricted',
capabilitiesGranted: ['prompt:inject'],
manifest,
fsPath: '/tmp/test',
installedAt: 0,
updatedAt: 0,
};
}
describe('GenUISurfaceSpec.component (manifest schema)', () => {
it('accepts a component path + export + sandbox triple', () => {
const result = GenUISurfaceSpecSchema.safeParse({
id: 'critique-panel',
kind: 'choice',
persist: 'run',
component: { path: './surfaces/critique-panel.tsx', export: 'CritiquePanel', sandbox: 'react' },
});
expect(result.success).toBe(true);
});
it('rejects an empty component.path', () => {
const result = GenUISurfaceSpecSchema.safeParse({
id: 'critique-panel',
kind: 'choice',
persist: 'run',
component: { path: '' },
});
expect(result.success).toBe(false);
});
});
describe('validateCapabilityList — genui:custom-component', () => {
it('treats genui:custom-component as a first-class top-level capability', () => {
const { accepted, rejected } = validateCapabilityList([
'prompt:inject',
'genui:custom-component',
]);
expect(accepted.sort()).toEqual(['genui:custom-component', 'prompt:inject']);
expect(rejected).toEqual([]);
});
});
describe('doctorPlugin — component capability gate', () => {
const baseManifest: PluginManifest = {
name: 'sample-plugin',
title: 'Sample',
version: '1.0.0',
description: 'fixture',
od: {
kind: 'skill',
genui: {
surfaces: [
{
id: 'critique-panel',
kind: 'choice',
persist: 'run',
component: { path: './surfaces/critique-panel.tsx' },
},
],
},
capabilities: ['prompt:inject'],
},
};
it('errors when a surface ships a component without genui:custom-component', () => {
expect(validateSafe(baseManifest).ok).toBe(true);
const report = doctorPlugin(pluginRecord(baseManifest), REGISTRY);
const codes = report.issues.map((d) => d.code);
expect(codes).toContain('genui.component-capability');
expect(report.ok).toBe(false);
});
it('passes when the matching capability is declared', () => {
const m: PluginManifest = {
...baseManifest,
od: { ...baseManifest.od, capabilities: ['prompt:inject', 'genui:custom-component'] },
};
const report = doctorPlugin(pluginRecord(m), REGISTRY);
expect(report.issues.find((d) => d.code === 'genui.component-capability')).toBeUndefined();
});
it('errors on path-traversal segments inside the component path', () => {
const m: PluginManifest = {
...baseManifest,
od: {
...baseManifest.od,
capabilities: ['prompt:inject', 'genui:custom-component'],
genui: {
surfaces: [
{
id: 'critique-panel',
kind: 'choice',
persist: 'run',
component: { path: '../escape/panel.tsx' },
},
],
},
},
};
const report = doctorPlugin(pluginRecord(m), REGISTRY);
expect(report.issues.find((d) => d.code === 'genui.component-traversal')).toBeDefined();
expect(report.ok).toBe(false);
});
});