From 841f47b3782a8b85c3badffdc8a9728c07e5de90 Mon Sep 17 00:00:00 2001 From: Mike Long Date: Tue, 17 Feb 2026 17:58:41 -0800 Subject: [PATCH] Add icon source policy validation support --- .../dist/commands/validate.js | 11 + .../dist/descriptors/static-analysis.d.ts.map | 2 +- .../dist/descriptors/static-analysis.js | 232 +++++++++++++ .../dist/utils/violation-classifier.d.ts.map | 2 +- .../dist/utils/violation-classifier.js | 1 + .../interfacectl-cli/src/commands/validate.ts | 11 + .../src/descriptors/static-analysis.ts | 314 ++++++++++++++++++ .../src/utils/violation-classifier.ts | 1 + .../interfacectl-validator/dist/index.d.ts | 2 +- .../dist/index.d.ts.map | 2 +- packages/interfacectl-validator/dist/index.js | 31 ++ .../schema/web.surface.contract.schema.json | 37 +++ .../interfacectl-validator/dist/types.d.ts | 20 +- .../dist/types.d.ts.map | 2 +- packages/interfacectl-validator/src/index.ts | 42 +++ .../schema/web.surface.contract.schema.json | 37 +++ packages/interfacectl-validator/src/types.ts | 21 ++ 17 files changed, 762 insertions(+), 6 deletions(-) diff --git a/packages/interfacectl-cli/dist/commands/validate.js b/packages/interfacectl-cli/dist/commands/validate.js index 47a90c6..1bb83d6 100644 --- a/packages/interfacectl-cli/dist/commands/validate.js +++ b/packages/interfacectl-cli/dist/commands/validate.js @@ -280,6 +280,7 @@ function mapViolationsToFindings(summary) { "unknown-section": "section.unexpected", "font-not-allowed": "font.disallowed", "color-not-allowed": "color.disallowed", + "icon-source-not-allowed": "icon.source-disallowed", "layout-width-exceeded": "layout.width-exceeded", "layout-width-undetermined": "layout.width-undetermined", "layout-container-missing": "layout.container-missing", @@ -335,6 +336,16 @@ function mapViolationsToFindings(summary) { } break; } + case "icon-source-not-allowed": { + finding.expected = Array.isArray(details.allowedSources) + ? details.allowedSources + : undefined; + finding.found = details.iconSource; + if (details.policy === "warn") { + finding.severity = "warning"; + } + break; + } case "layout-width-undetermined": { finding.expected = details.expectedMaxWidth; finding.found = null; diff --git a/packages/interfacectl-cli/dist/descriptors/static-analysis.d.ts.map b/packages/interfacectl-cli/dist/descriptors/static-analysis.d.ts.map index 1840a42..d45b04d 100644 --- a/packages/interfacectl-cli/dist/descriptors/static-analysis.d.ts.map +++ b/packages/interfacectl-cli/dist/descriptors/static-analysis.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"static-analysis.d.ts","sourceRoot":"","sources":["../../src/descriptors/static-analysis.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EAQvB,MAAM,kCAAkC,CAAC;AA+E1C,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gCAAgC;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,+BAA+B;IAC9C,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,+BAA+B,CAAC,CA0C1C"} \ No newline at end of file +{"version":3,"file":"static-analysis.d.ts","sourceRoot":"","sources":["../../src/descriptors/static-analysis.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EASvB,MAAM,kCAAkC,CAAC;AA6G1C,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gCAAgC;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,+BAA+B;IAC9C,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,+BAA+B,CAAC,CA0C1C"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/descriptors/static-analysis.js b/packages/interfacectl-cli/dist/descriptors/static-analysis.js index 401dae1..f644b29 100644 --- a/packages/interfacectl-cli/dist/descriptors/static-analysis.js +++ b/packages/interfacectl-cli/dist/descriptors/static-analysis.js @@ -64,6 +64,32 @@ const PRIMITIVE_PATTERNS = [ { role: "sidebar", regex: ASIDE_REGEX }, { role: "auth-shell", regex: AUTH_SHELL_REGEX }, ]; +const IMPORT_SOURCE_REGEX = /\bimport\s+(?:type\s+)?(?:[^"'`]*?\s+from\s+)?["'`]([^"'`]+)["'`]/g; +const EXPORT_SOURCE_REGEX = /\bexport\s+(?:\*|\{[^}]*\})\s+from\s+["'`]([^"'`]+)["'`]/g; +const SURFACES_UI_COMPONENT_PREFIX = "@surfaces/ui/components/"; +const MODULE_EXTENSIONS = [ + ".ts", + ".tsx", + ".js", + ".jsx", + ".mjs", + ".mts", + ".cjs", + ".cts", +]; +const INDEX_MODULE_BASENAMES = MODULE_EXTENSIONS.map((ext) => `index${ext}`); +const ICON_LIBRARY_PATTERNS = [ + /^lucide-react$/, + /^@heroicons\//, + /^react-icons(?:\/|$)/, + /^@tabler\/icons-react$/, + /^@mui\/icons-material(?:\/|$)/, + /^@phosphor-icons\//, + /^phosphor-react$/, + /^iconoir-react$/, + /^remixicon-react$/, + /^@fortawesome\//, +]; export async function collectSurfaceDescriptors(options) { const structuralDescriptors = []; const warnings = []; @@ -155,11 +181,14 @@ async function extractSurfaceDescriptor(workspaceRoot, surfaceRoot, surfaceId, s }); } const primitives = await extractPrimitives(sectionFiles, workspaceRoot, fileContentCache); + const { icons, warnings: iconWarnings } = await extractIconSources(surfaceRoot, workspaceRoot, fileContentCache, surfaceId); + warnings.push(...iconWarnings); const structuralSurfaceDescriptor = { surfaceId, sections, fonts, colors, + icons, layout, motion, primitives, @@ -711,6 +740,200 @@ async function extractPrimitives(sectionFiles, workspaceRoot, fileContentCache) })) .sort((a, b) => a.role.localeCompare(b.role)); } +async function extractIconSources(surfaceRoot, workspaceRoot, fileContentCache, surfaceId) { + const warnings = []; + const iconSources = new Map(); + const unresolvedSharedImports = new Set(); + const sharedUiRoots = await findSharedUiSourceRoots(workspaceRoot); + const sourceFiles = await globby(["**/*.{ts,tsx,js,jsx,mts,mjs,cjs,cts}"], { + cwd: surfaceRoot, + absolute: true, + gitignore: true, + ignore: COMMON_GLOBBY_IGNORES, + }); + const sharedQueue = []; + const queuedSharedFiles = new Set(); + const enqueueSharedFile = (filePath) => { + if (!queuedSharedFiles.has(filePath)) { + queuedSharedFiles.add(filePath); + sharedQueue.push(filePath); + } + }; + for (const filePath of sourceFiles) { + const content = await readFileCached(filePath, fileContentCache); + const fileSource = path.relative(workspaceRoot, filePath); + const importSpecifiers = parseModuleSpecifiers(content); + for (const specifier of importSpecifiers) { + if (specifier.startsWith(SURFACES_UI_COMPONENT_PREFIX)) { + const resolved = await resolveSharedUiComponentImport(specifier, sharedUiRoots); + if (resolved) { + enqueueSharedFile(resolved); + } + else { + unresolvedSharedImports.add(specifier); + } + continue; + } + if (isExternalModuleSpecifier(specifier) && + isIconLibrarySpecifier(specifier)) { + if (!iconSources.has(specifier)) { + iconSources.set(specifier, { + value: specifier, + source: fileSource, + }); + } + } + } + } + while (sharedQueue.length > 0) { + const filePath = sharedQueue.shift(); + if (!filePath) { + continue; + } + const content = await readFileCached(filePath, fileContentCache); + const fileSource = path.relative(workspaceRoot, filePath); + const importSpecifiers = parseModuleSpecifiers(content); + for (const specifier of importSpecifiers) { + if (isExternalModuleSpecifier(specifier) && + isIconLibrarySpecifier(specifier)) { + if (!iconSources.has(specifier)) { + iconSources.set(specifier, { + value: specifier, + source: fileSource, + }); + } + continue; + } + if (specifier.startsWith(SURFACES_UI_COMPONENT_PREFIX)) { + const resolved = await resolveSharedUiComponentImport(specifier, sharedUiRoots); + if (resolved) { + enqueueSharedFile(resolved); + } + else { + unresolvedSharedImports.add(specifier); + } + continue; + } + if (specifier.startsWith(".")) { + const resolved = await resolveModulePath(path.dirname(filePath), specifier); + if (resolved && isPathWithinRoots(resolved, sharedUiRoots)) { + enqueueSharedFile(resolved); + } + } + } + } + for (const specifier of [...unresolvedSharedImports].sort((a, b) => a.localeCompare(b))) { + warnings.push({ + surfaceId, + code: "icons.shared-ui-unresolved", + message: `Shared UI import "${specifier}" could not be resolved; icon source detection may be incomplete for surface "${surfaceId}".`, + }); + } + return { + icons: [...iconSources.values()].sort((a, b) => a.value.localeCompare(b.value)), + warnings, + }; +} +function parseModuleSpecifiers(content) { + const specifiers = new Set(); + IMPORT_SOURCE_REGEX.lastIndex = 0; + EXPORT_SOURCE_REGEX.lastIndex = 0; + let match; + while ((match = IMPORT_SOURCE_REGEX.exec(content)) !== null) { + if (match[1]) { + specifiers.add(match[1]); + } + } + while ((match = EXPORT_SOURCE_REGEX.exec(content)) !== null) { + if (match[1]) { + specifiers.add(match[1]); + } + } + return [...specifiers]; +} +function isExternalModuleSpecifier(specifier) { + if (specifier.startsWith(".") || + specifier.startsWith("/") || + specifier.startsWith("@/") || + specifier.startsWith("~/")) { + return false; + } + return true; +} +function isIconLibrarySpecifier(specifier) { + if (ICON_LIBRARY_PATTERNS.some((pattern) => pattern.test(specifier))) { + return true; + } + const normalized = specifier.toLowerCase(); + if (normalized.startsWith("@surfaces/ui")) { + return false; + } + return /(^|\/)icons?(\/|$)/.test(normalized); +} +async function findSharedUiSourceRoots(workspaceRoot) { + const candidates = [ + path.join(workspaceRoot, "packages", "ui", "src"), + path.join(workspaceRoot, "..", "packages", "ui", "src"), + path.join(workspaceRoot, "..", "surfaces-webapps", "packages", "ui", "src"), + path.join(workspaceRoot, "..", "..", "surfaces-webapps", "packages", "ui", "src"), + ]; + const roots = []; + const seen = new Set(); + for (const candidate of candidates) { + const resolved = path.resolve(candidate); + if (seen.has(resolved)) { + continue; + } + seen.add(resolved); + if (await pathExists(resolved)) { + roots.push(resolved); + } + } + return roots; +} +async function resolveSharedUiComponentImport(specifier, sharedUiRoots) { + if (!specifier.startsWith(SURFACES_UI_COMPONENT_PREFIX)) { + return undefined; + } + const componentPath = specifier.slice(SURFACES_UI_COMPONENT_PREFIX.length); + for (const root of sharedUiRoots) { + const resolved = await resolveModulePath(root, path.join("components", componentPath)); + if (resolved) { + return resolved; + } + } + return undefined; +} +async function resolveModulePath(baseDir, targetPath) { + const absolutePath = path.resolve(baseDir, targetPath); + return resolveWithExtensions(absolutePath); +} +async function resolveWithExtensions(absolutePath) { + const directCandidates = [absolutePath]; + if (path.extname(absolutePath).length === 0) { + for (const ext of MODULE_EXTENSIONS) { + directCandidates.push(`${absolutePath}${ext}`); + } + } + for (const candidate of directCandidates) { + if (await pathIsFile(candidate)) { + return candidate; + } + } + for (const basename of INDEX_MODULE_BASENAMES) { + const indexCandidate = path.join(absolutePath, basename); + if (await pathIsFile(indexCandidate)) { + return indexCandidate; + } + } + return undefined; +} +function isPathWithinRoots(filePath, roots) { + return roots.some((root) => { + const relative = path.relative(root, filePath); + return Boolean(relative) && !relative.startsWith(".."); + }); +} function parseColorValue(value) { const colors = []; const trimmed = value.trim(); @@ -851,6 +1074,15 @@ async function pathExists(filePath) { return false; } } +async function pathIsFile(filePath) { + try { + const fileStat = await stat(filePath); + return fileStat.isFile(); + } + catch { + return false; + } +} function parseDurationToMs(value, unit) { const numericValue = Number.parseFloat(value); if (!Number.isFinite(numericValue)) { diff --git a/packages/interfacectl-cli/dist/utils/violation-classifier.d.ts.map b/packages/interfacectl-cli/dist/utils/violation-classifier.d.ts.map index d7efbbd..82194e9 100644 --- a/packages/interfacectl-cli/dist/utils/violation-classifier.d.ts.map +++ b/packages/interfacectl-cli/dist/utils/violation-classifier.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"violation-classifier.d.ts","sourceRoot":"","sources":["../../src/utils/violation-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,GACvB,IAAI,GAAG,IAAI,CAgBb;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,eAAe,GACvB,MAAM,CA4BR;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,EAAE,GACnB,QAAQ,GAAG,IAAI,CAwBjB"} \ No newline at end of file +{"version":3,"file":"violation-classifier.d.ts","sourceRoot":"","sources":["../../src/utils/violation-classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,GACvB,IAAI,GAAG,IAAI,CAiBb;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,eAAe,GACvB,MAAM,CA4BR;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,SAAS,EAAE,GACnB,QAAQ,GAAG,IAAI,CAwBjB"} \ No newline at end of file diff --git a/packages/interfacectl-cli/dist/utils/violation-classifier.js b/packages/interfacectl-cli/dist/utils/violation-classifier.js index f870284..95a5b7c 100644 --- a/packages/interfacectl-cli/dist/utils/violation-classifier.js +++ b/packages/interfacectl-cli/dist/utils/violation-classifier.js @@ -8,6 +8,7 @@ export function classifyViolationType(type) { const e1Types = [ "font-not-allowed", "color-not-allowed", + "icon-source-not-allowed", "motion-duration-not-allowed", "motion-timing-not-allowed", ]; diff --git a/packages/interfacectl-cli/src/commands/validate.ts b/packages/interfacectl-cli/src/commands/validate.ts index 15c0380..8addbad 100644 --- a/packages/interfacectl-cli/src/commands/validate.ts +++ b/packages/interfacectl-cli/src/commands/validate.ts @@ -420,6 +420,7 @@ function mapViolationsToFindings( "unknown-section": "section.unexpected", "font-not-allowed": "font.disallowed", "color-not-allowed": "color.disallowed", + "icon-source-not-allowed": "icon.source-disallowed", "layout-width-exceeded": "layout.width-exceeded", "layout-width-undetermined": "layout.width-undetermined", "layout-container-missing": "layout.container-missing", @@ -479,6 +480,16 @@ function mapViolationsToFindings( } break; } + case "icon-source-not-allowed": { + finding.expected = Array.isArray(details.allowedSources) + ? details.allowedSources + : undefined; + finding.found = details.iconSource; + if (details.policy === "warn") { + finding.severity = "warning"; + } + break; + } case "layout-width-undetermined": { finding.expected = details.expectedMaxWidth; finding.found = null; diff --git a/packages/interfacectl-cli/src/descriptors/static-analysis.ts b/packages/interfacectl-cli/src/descriptors/static-analysis.ts index 6810d2f..6c4b252 100644 --- a/packages/interfacectl-cli/src/descriptors/static-analysis.ts +++ b/packages/interfacectl-cli/src/descriptors/static-analysis.ts @@ -6,6 +6,7 @@ import { type SurfaceDescriptor, type SurfaceFontDescriptor, type SurfaceColorDescriptor, + type SurfaceIconDescriptor, type SurfaceLayoutDescriptor, type SurfaceMotionDescriptor, type SurfaceSectionDescriptor, @@ -89,6 +90,36 @@ const PRIMITIVE_PATTERNS: Array<{ role: string; regex: RegExp }> = [ { role: "sidebar", regex: ASIDE_REGEX }, { role: "auth-shell", regex: AUTH_SHELL_REGEX }, ]; +const IMPORT_SOURCE_REGEX = + /\bimport\s+(?:type\s+)?(?:[^"'`]*?\s+from\s+)?["'`]([^"'`]+)["'`]/g; +const EXPORT_SOURCE_REGEX = + /\bexport\s+(?:\*|\{[^}]*\})\s+from\s+["'`]([^"'`]+)["'`]/g; +const SURFACES_UI_COMPONENT_PREFIX = "@surfaces/ui/components/"; +const MODULE_EXTENSIONS = [ + ".ts", + ".tsx", + ".js", + ".jsx", + ".mjs", + ".mts", + ".cjs", + ".cts", +]; +const INDEX_MODULE_BASENAMES = MODULE_EXTENSIONS.map( + (ext) => `index${ext}`, +); +const ICON_LIBRARY_PATTERNS = [ + /^lucide-react$/, + /^@heroicons\//, + /^react-icons(?:\/|$)/, + /^@tabler\/icons-react$/, + /^@mui\/icons-material(?:\/|$)/, + /^@phosphor-icons\//, + /^phosphor-react$/, + /^iconoir-react$/, + /^remixicon-react$/, + /^@fortawesome\//, +]; export interface DescriptorIssue { surfaceId?: string; @@ -264,12 +295,20 @@ async function extractSurfaceDescriptor( } const primitives = await extractPrimitives(sectionFiles, workspaceRoot, fileContentCache); + const { icons, warnings: iconWarnings } = await extractIconSources( + surfaceRoot, + workspaceRoot, + fileContentCache, + surfaceId, + ); + warnings.push(...iconWarnings); const structuralSurfaceDescriptor: SurfaceDescriptor = { surfaceId, sections, fonts, colors, + icons, layout, motion, primitives, @@ -969,6 +1008,272 @@ async function extractPrimitives( .sort((a, b) => a.role.localeCompare(b.role)); } +async function extractIconSources( + surfaceRoot: string, + workspaceRoot: string, + fileContentCache: Map, + surfaceId: string, +): Promise<{ + icons: SurfaceIconDescriptor[]; + warnings: DescriptorIssue[]; +}> { + const warnings: DescriptorIssue[] = []; + const iconSources = new Map(); + const unresolvedSharedImports = new Set(); + const sharedUiRoots = await findSharedUiSourceRoots(workspaceRoot); + + const sourceFiles = await globby(["**/*.{ts,tsx,js,jsx,mts,mjs,cjs,cts}"], { + cwd: surfaceRoot, + absolute: true, + gitignore: true, + ignore: COMMON_GLOBBY_IGNORES, + }); + + const sharedQueue: string[] = []; + const queuedSharedFiles = new Set(); + + const enqueueSharedFile = (filePath: string) => { + if (!queuedSharedFiles.has(filePath)) { + queuedSharedFiles.add(filePath); + sharedQueue.push(filePath); + } + }; + + for (const filePath of sourceFiles) { + const content = await readFileCached(filePath, fileContentCache); + const fileSource = path.relative(workspaceRoot, filePath); + const importSpecifiers = parseModuleSpecifiers(content); + + for (const specifier of importSpecifiers) { + if (specifier.startsWith(SURFACES_UI_COMPONENT_PREFIX)) { + const resolved = await resolveSharedUiComponentImport( + specifier, + sharedUiRoots, + ); + if (resolved) { + enqueueSharedFile(resolved); + } else { + unresolvedSharedImports.add(specifier); + } + continue; + } + + if ( + isExternalModuleSpecifier(specifier) && + isIconLibrarySpecifier(specifier) + ) { + if (!iconSources.has(specifier)) { + iconSources.set(specifier, { + value: specifier, + source: fileSource, + }); + } + } + } + } + + while (sharedQueue.length > 0) { + const filePath = sharedQueue.shift(); + if (!filePath) { + continue; + } + + const content = await readFileCached(filePath, fileContentCache); + const fileSource = path.relative(workspaceRoot, filePath); + const importSpecifiers = parseModuleSpecifiers(content); + + for (const specifier of importSpecifiers) { + if ( + isExternalModuleSpecifier(specifier) && + isIconLibrarySpecifier(specifier) + ) { + if (!iconSources.has(specifier)) { + iconSources.set(specifier, { + value: specifier, + source: fileSource, + }); + } + continue; + } + + if (specifier.startsWith(SURFACES_UI_COMPONENT_PREFIX)) { + const resolved = await resolveSharedUiComponentImport( + specifier, + sharedUiRoots, + ); + if (resolved) { + enqueueSharedFile(resolved); + } else { + unresolvedSharedImports.add(specifier); + } + continue; + } + + if (specifier.startsWith(".")) { + const resolved = await resolveModulePath(path.dirname(filePath), specifier); + if (resolved && isPathWithinRoots(resolved, sharedUiRoots)) { + enqueueSharedFile(resolved); + } + } + } + } + + for (const specifier of [...unresolvedSharedImports].sort((a, b) => + a.localeCompare(b), + )) { + warnings.push({ + surfaceId, + code: "icons.shared-ui-unresolved", + message: `Shared UI import "${specifier}" could not be resolved; icon source detection may be incomplete for surface "${surfaceId}".`, + }); + } + + return { + icons: [...iconSources.values()].sort((a, b) => + a.value.localeCompare(b.value), + ), + warnings, + }; +} + +function parseModuleSpecifiers(content: string): string[] { + const specifiers = new Set(); + IMPORT_SOURCE_REGEX.lastIndex = 0; + EXPORT_SOURCE_REGEX.lastIndex = 0; + + let match: RegExpExecArray | null; + while ((match = IMPORT_SOURCE_REGEX.exec(content)) !== null) { + if (match[1]) { + specifiers.add(match[1]); + } + } + while ((match = EXPORT_SOURCE_REGEX.exec(content)) !== null) { + if (match[1]) { + specifiers.add(match[1]); + } + } + + return [...specifiers]; +} + +function isExternalModuleSpecifier(specifier: string): boolean { + if ( + specifier.startsWith(".") || + specifier.startsWith("/") || + specifier.startsWith("@/") || + specifier.startsWith("~/") + ) { + return false; + } + return true; +} + +function isIconLibrarySpecifier(specifier: string): boolean { + if (ICON_LIBRARY_PATTERNS.some((pattern) => pattern.test(specifier))) { + return true; + } + + const normalized = specifier.toLowerCase(); + if (normalized.startsWith("@surfaces/ui")) { + return false; + } + + return /(^|\/)icons?(\/|$)/.test(normalized); +} + +async function findSharedUiSourceRoots(workspaceRoot: string): Promise { + const candidates = [ + path.join(workspaceRoot, "packages", "ui", "src"), + path.join(workspaceRoot, "..", "packages", "ui", "src"), + path.join(workspaceRoot, "..", "surfaces-webapps", "packages", "ui", "src"), + path.join( + workspaceRoot, + "..", + "..", + "surfaces-webapps", + "packages", + "ui", + "src", + ), + ]; + + const roots: string[] = []; + const seen = new Set(); + for (const candidate of candidates) { + const resolved = path.resolve(candidate); + if (seen.has(resolved)) { + continue; + } + seen.add(resolved); + if (await pathExists(resolved)) { + roots.push(resolved); + } + } + return roots; +} + +async function resolveSharedUiComponentImport( + specifier: string, + sharedUiRoots: string[], +): Promise { + if (!specifier.startsWith(SURFACES_UI_COMPONENT_PREFIX)) { + return undefined; + } + + const componentPath = specifier.slice(SURFACES_UI_COMPONENT_PREFIX.length); + for (const root of sharedUiRoots) { + const resolved = await resolveModulePath( + root, + path.join("components", componentPath), + ); + if (resolved) { + return resolved; + } + } + return undefined; +} + +async function resolveModulePath( + baseDir: string, + targetPath: string, +): Promise { + const absolutePath = path.resolve(baseDir, targetPath); + return resolveWithExtensions(absolutePath); +} + +async function resolveWithExtensions( + absolutePath: string, +): Promise { + const directCandidates = [absolutePath]; + if (path.extname(absolutePath).length === 0) { + for (const ext of MODULE_EXTENSIONS) { + directCandidates.push(`${absolutePath}${ext}`); + } + } + + for (const candidate of directCandidates) { + if (await pathIsFile(candidate)) { + return candidate; + } + } + + for (const basename of INDEX_MODULE_BASENAMES) { + const indexCandidate = path.join(absolutePath, basename); + if (await pathIsFile(indexCandidate)) { + return indexCandidate; + } + } + + return undefined; +} + +function isPathWithinRoots(filePath: string, roots: string[]): boolean { + return roots.some((root) => { + const relative = path.relative(root, filePath); + return Boolean(relative) && !relative.startsWith(".."); + }); +} + function parseColorValue(value: string): string[] { const colors: string[] = []; const trimmed = value.trim(); @@ -1126,6 +1431,15 @@ async function pathExists(filePath: string): Promise { } } +async function pathIsFile(filePath: string): Promise { + try { + const fileStat = await stat(filePath); + return fileStat.isFile(); + } catch { + return false; + } +} + function parseDurationToMs(value: string, unit: string): number | null { const numericValue = Number.parseFloat(value); if (!Number.isFinite(numericValue)) { diff --git a/packages/interfacectl-cli/src/utils/violation-classifier.ts b/packages/interfacectl-cli/src/utils/violation-classifier.ts index 52be6f0..5579f5d 100644 --- a/packages/interfacectl-cli/src/utils/violation-classifier.ts +++ b/packages/interfacectl-cli/src/utils/violation-classifier.ts @@ -15,6 +15,7 @@ export function classifyViolationType( const e1Types: DriftViolationType[] = [ "font-not-allowed", "color-not-allowed", + "icon-source-not-allowed", "motion-duration-not-allowed", "motion-timing-not-allowed", ]; diff --git a/packages/interfacectl-validator/dist/index.d.ts b/packages/interfacectl-validator/dist/index.d.ts index 861fd83..ff6305f 100644 --- a/packages/interfacectl-validator/dist/index.d.ts +++ b/packages/interfacectl-validator/dist/index.d.ts @@ -8,7 +8,7 @@ export interface ContractStructureValidation { export declare function validateContractStructure(contractData: unknown, schema: object): ContractStructureValidation; export declare function evaluateSurfaceCompliance(contract: InterfaceContract, descriptor: SurfaceDescriptor): SurfaceReport; export declare function evaluateContractCompliance(contract: InterfaceContract, descriptors: SurfaceDescriptor[]): ValidationSummary; -export type { InterfaceContract, ContractSurface, ContractSection, ContractConstraints, SurfaceDescriptor, SurfaceSectionDescriptor, SurfaceFontDescriptor, SurfaceColorDescriptor, SurfaceMotionDescriptor, SurfaceLayoutDescriptor, PageFrameLayoutDescriptor, SurfacePrimitiveDescriptor, SurfaceReport, DriftViolation, ValidationSummary, DriftViolationType, ContractRef, RuleRef, DiffOutput, DiffEntry, DiffChangeType, DriftRisk, Severity, SafetyLevel, EnforcementPolicy, EnforcementMode, AutofixRule, FixSummary, FixEntry, FixError, } from "./types.js"; +export type { InterfaceContract, ContractSurface, ContractSection, ContractConstraints, SurfaceDescriptor, SurfaceSectionDescriptor, SurfaceFontDescriptor, SurfaceColorDescriptor, SurfaceIconDescriptor, SurfaceMotionDescriptor, SurfaceLayoutDescriptor, PageFrameLayoutDescriptor, SurfacePrimitiveDescriptor, SurfaceReport, DriftViolation, ValidationSummary, DriftViolationType, ContractRef, RuleRef, DiffOutput, DiffEntry, DiffChangeType, DriftRisk, Severity, SafetyLevel, EnforcementPolicy, EnforcementMode, IconPolicy, AutofixRule, FixSummary, FixEntry, FixError, } from "./types.js"; export { getBundledDiffSchema, getBundledPolicySchema, getBundledFixSummarySchema, validateDiffOutput, validatePolicy, validateFixSummary, type ValidationResult, } from "./schema-validate.js"; export { normalizeColorValue, normalizeColorValues, } from "./color-policy.js"; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/interfacectl-validator/dist/index.d.ts.map b/packages/interfacectl-validator/dist/index.d.ts.map index 4e7c149..efd0aa5 100644 --- a/packages/interfacectl-validator/dist/index.d.ts.map +++ b/packages/interfacectl-validator/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AAQpB,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,OAAO,EACrB,MAAM,EAAE,MAAM,GACb,2BAA2B,CAyB7B;AAqCD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,iBAAiB,GAC5B,aAAa,CAoXf;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,iBAAiB,EAAE,GAC/B,iBAAiB,CA6CnB;AAyCD,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,yBAAyB,EACzB,0BAA0B,EAC1B,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,OAAO,EACP,UAAU,EACV,SAAS,EACT,cAAc,EACd,SAAS,EACT,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,GACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EAKlB,MAAM,YAAY,CAAC;AAQpB,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,OAAO,EACrB,MAAM,EAAE,MAAM,GACb,2BAA2B,CAyB7B;AA4ED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,iBAAiB,EAC3B,UAAU,EAAE,iBAAiB,GAC5B,aAAa,CAqXf;AAED,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,iBAAiB,EAAE,GAC/B,iBAAiB,CA6CnB;AAyCD,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,yBAAyB,EACzB,0BAA0B,EAC1B,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,OAAO,EACP,UAAU,EACV,SAAS,EACT,cAAc,EACd,SAAS,EACT,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,GACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC"} \ No newline at end of file diff --git a/packages/interfacectl-validator/dist/index.js b/packages/interfacectl-validator/dist/index.js index ac51f63..7f0f830 100644 --- a/packages/interfacectl-validator/dist/index.js +++ b/packages/interfacectl-validator/dist/index.js @@ -53,6 +53,36 @@ function validateColorPolicy(contract, descriptor, violations) { }); } } +function validateIconPolicy(surface, descriptor, violations) { + const iconPolicy = surface.icons; + if (surface.type !== "web" || !iconPolicy || iconPolicy.policy === "off") { + return; + } + const allowedSources = new Set(iconPolicy.allowedSources); + const seenSources = new Set(); + for (const icon of descriptor.icons ?? []) { + const iconSource = icon.value.trim(); + if (!iconSource || seenSources.has(iconSource)) { + continue; + } + seenSources.add(iconSource); + if (allowedSources.has(iconSource)) { + continue; + } + violations.push({ + surfaceId: descriptor.surfaceId, + type: "icon-source-not-allowed", + message: `Icon source "${iconSource}" is not allowed for surface "${descriptor.surfaceId}".`, + details: { + iconSource, + source: icon.source, + allowedSources: [...allowedSources], + policy: iconPolicy.policy, + jsonPointer: `/surfaces/${descriptor.surfaceId}/icons/allowedSources`, + }, + }); + } +} export function evaluateSurfaceCompliance(contract, descriptor) { const surface = findSurface(contract.surfaces, descriptor.surfaceId); const violations = []; @@ -173,6 +203,7 @@ export function evaluateSurfaceCompliance(contract, descriptor) { } } validateColorPolicy(contract, descriptor, violations); + validateIconPolicy(surface, descriptor, violations); const reportedWidth = descriptor.layout.maxContentWidth; if (reportedWidth === null || reportedWidth === undefined) { violations.push({ diff --git a/packages/interfacectl-validator/dist/schema/web.surface.contract.schema.json b/packages/interfacectl-validator/dist/schema/web.surface.contract.schema.json index 2e785dc..7564e7c 100644 --- a/packages/interfacectl-validator/dist/schema/web.surface.contract.schema.json +++ b/packages/interfacectl-validator/dist/schema/web.surface.contract.schema.json @@ -73,6 +73,14 @@ }, "authAware": { "type": "boolean" + }, + "iconSources": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true } } }, @@ -150,6 +158,9 @@ } } }, + "icons": { + "$ref": "#/$defs/iconPolicy" + }, "phase0": { "type": "object", "description": "Phase 0.5: optional expectations for extracted reality (routes, auth, shell, design system).", @@ -301,6 +312,32 @@ } } }, + "iconPolicy": { + "type": "object", + "additionalProperties": false, + "required": [ + "policy", + "allowedSources" + ], + "properties": { + "policy": { + "type": "string", + "enum": [ + "off", + "warn", + "strict" + ] + }, + "allowedSources": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + } + } + }, "color": { "type": "object", "additionalProperties": false, diff --git a/packages/interfacectl-validator/dist/types.d.ts b/packages/interfacectl-validator/dist/types.d.ts index f850c6b..8940282 100644 --- a/packages/interfacectl-validator/dist/types.d.ts +++ b/packages/interfacectl-validator/dist/types.d.ts @@ -17,6 +17,7 @@ export interface ContractSurface { requiredContainers?: string[]; pageFrame?: PageFrameLayout; }; + icons?: IconPolicy; mustNotEmit?: string[]; shellOwnedPrimitiveAllowSources?: string[]; } @@ -39,6 +40,10 @@ export interface ColorPolicy { policy: "off" | "warn" | "strict"; allowedValues: string[]; } +export interface IconPolicy { + policy: "off" | "warn" | "strict"; + allowedSources: string[]; +} export interface InterfaceContract { contractId: string; version: string; @@ -47,6 +52,14 @@ export interface InterfaceContract { sections: ContractSection[]; constraints: ContractConstraints; color: ColorPolicy; + x_extracted?: { + routes?: string[]; + hasShell?: boolean; + designSystemComponents?: string[]; + authAware?: boolean; + iconSources?: string[]; + [key: string]: unknown; + }; shell?: ShellSpec; } export interface SurfaceSectionDescriptor { @@ -61,6 +74,10 @@ export interface SurfaceColorDescriptor { value: string; source?: string; } +export interface SurfaceIconDescriptor { + value: string; + source?: string; +} export interface SurfaceMotionDescriptor { durationMs: number; timingFunction: string; @@ -92,11 +109,12 @@ export interface SurfaceDescriptor { sections: SurfaceSectionDescriptor[]; fonts: SurfaceFontDescriptor[]; colors: SurfaceColorDescriptor[]; + icons?: SurfaceIconDescriptor[]; layout: SurfaceLayoutDescriptor; motion: SurfaceMotionDescriptor[]; primitives?: SurfacePrimitiveDescriptor[]; } -export type DriftViolationType = "unknown-surface" | "missing-section" | "unknown-section" | "font-not-allowed" | "color-not-allowed" | "layout-width-exceeded" | "layout-width-undetermined" | "layout-container-missing" | "layout-pageframe-selector-unsupported" | "layout-pageframe-container-not-found" | "layout-pageframe-maxwidth-mismatch" | "layout-pageframe-padding-mismatch" | "layout-pageframe-non-deterministic-value" | "layout-pageframe-unextractable-value" | "motion-duration-not-allowed" | "motion-timing-not-allowed" | "descriptor-missing" | "descriptor-unused" | "shell-owned-primitive-emitted"; +export type DriftViolationType = "unknown-surface" | "missing-section" | "unknown-section" | "font-not-allowed" | "color-not-allowed" | "icon-source-not-allowed" | "layout-width-exceeded" | "layout-width-undetermined" | "layout-container-missing" | "layout-pageframe-selector-unsupported" | "layout-pageframe-container-not-found" | "layout-pageframe-maxwidth-mismatch" | "layout-pageframe-padding-mismatch" | "layout-pageframe-non-deterministic-value" | "layout-pageframe-unextractable-value" | "motion-duration-not-allowed" | "motion-timing-not-allowed" | "descriptor-missing" | "descriptor-unused" | "shell-owned-primitive-emitted"; export interface DriftViolation { surfaceId: string; type: DriftViolationType; diff --git a/packages/interfacectl-validator/dist/types.d.ts.map b/packages/interfacectl-validator/dist/types.d.ts.map index e3d217f..2f6fbd3 100644 --- a/packages/interfacectl-validator/dist/types.d.ts.map +++ b/packages/interfacectl-validator/dist/types.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/interfacectl-validator/src/index.ts b/packages/interfacectl-validator/src/index.ts index 15adcc4..16a2a9e 100644 --- a/packages/interfacectl-validator/src/index.ts +++ b/packages/interfacectl-validator/src/index.ts @@ -92,6 +92,45 @@ function validateColorPolicy( } } +function validateIconPolicy( + surface: ContractSurface, + descriptor: SurfaceDescriptor, + violations: DriftViolation[], +): void { + const iconPolicy = surface.icons; + if (surface.type !== "web" || !iconPolicy || iconPolicy.policy === "off") { + return; + } + + const allowedSources = new Set(iconPolicy.allowedSources); + const seenSources = new Set(); + + for (const icon of descriptor.icons ?? []) { + const iconSource = icon.value.trim(); + if (!iconSource || seenSources.has(iconSource)) { + continue; + } + seenSources.add(iconSource); + + if (allowedSources.has(iconSource)) { + continue; + } + + violations.push({ + surfaceId: descriptor.surfaceId, + type: "icon-source-not-allowed", + message: `Icon source "${iconSource}" is not allowed for surface "${descriptor.surfaceId}".`, + details: { + iconSource, + source: icon.source, + allowedSources: [...allowedSources], + policy: iconPolicy.policy, + jsonPointer: `/surfaces/${descriptor.surfaceId}/icons/allowedSources`, + }, + }); + } +} + export function evaluateSurfaceCompliance( contract: InterfaceContract, descriptor: SurfaceDescriptor, @@ -227,6 +266,7 @@ export function evaluateSurfaceCompliance( } validateColorPolicy(contract, descriptor, violations); + validateIconPolicy(surface, descriptor, violations); const reportedWidth = descriptor.layout.maxContentWidth; if (reportedWidth === null || reportedWidth === undefined) { @@ -567,6 +607,7 @@ export type { SurfaceSectionDescriptor, SurfaceFontDescriptor, SurfaceColorDescriptor, + SurfaceIconDescriptor, SurfaceMotionDescriptor, SurfaceLayoutDescriptor, PageFrameLayoutDescriptor, @@ -585,6 +626,7 @@ export type { SafetyLevel, EnforcementPolicy, EnforcementMode, + IconPolicy, AutofixRule, FixSummary, FixEntry, diff --git a/packages/interfacectl-validator/src/schema/web.surface.contract.schema.json b/packages/interfacectl-validator/src/schema/web.surface.contract.schema.json index 2e785dc..7564e7c 100644 --- a/packages/interfacectl-validator/src/schema/web.surface.contract.schema.json +++ b/packages/interfacectl-validator/src/schema/web.surface.contract.schema.json @@ -73,6 +73,14 @@ }, "authAware": { "type": "boolean" + }, + "iconSources": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true } } }, @@ -150,6 +158,9 @@ } } }, + "icons": { + "$ref": "#/$defs/iconPolicy" + }, "phase0": { "type": "object", "description": "Phase 0.5: optional expectations for extracted reality (routes, auth, shell, design system).", @@ -301,6 +312,32 @@ } } }, + "iconPolicy": { + "type": "object", + "additionalProperties": false, + "required": [ + "policy", + "allowedSources" + ], + "properties": { + "policy": { + "type": "string", + "enum": [ + "off", + "warn", + "strict" + ] + }, + "allowedSources": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + } + } + }, "color": { "type": "object", "additionalProperties": false, diff --git a/packages/interfacectl-validator/src/types.ts b/packages/interfacectl-validator/src/types.ts index 130ad28..4ba344d 100644 --- a/packages/interfacectl-validator/src/types.ts +++ b/packages/interfacectl-validator/src/types.ts @@ -19,6 +19,7 @@ export interface ContractSurface { requiredContainers?: string[]; pageFrame?: PageFrameLayout; }; + icons?: IconPolicy; mustNotEmit?: string[]; shellOwnedPrimitiveAllowSources?: string[]; } @@ -46,6 +47,11 @@ export interface ColorPolicy { allowedValues: string[]; } +export interface IconPolicy { + policy: "off" | "warn" | "strict"; + allowedSources: string[]; +} + export interface InterfaceContract { contractId: string; version: string; @@ -54,6 +60,14 @@ export interface InterfaceContract { sections: ContractSection[]; constraints: ContractConstraints; color: ColorPolicy; + x_extracted?: { + routes?: string[]; + hasShell?: boolean; + designSystemComponents?: string[]; + authAware?: boolean; + iconSources?: string[]; + [key: string]: unknown; + }; shell?: ShellSpec; } @@ -72,6 +86,11 @@ export interface SurfaceColorDescriptor { source?: string; } +export interface SurfaceIconDescriptor { + value: string; + source?: string; +} + export interface SurfaceMotionDescriptor { durationMs: number; timingFunction: string; @@ -107,6 +126,7 @@ export interface SurfaceDescriptor { sections: SurfaceSectionDescriptor[]; fonts: SurfaceFontDescriptor[]; colors: SurfaceColorDescriptor[]; + icons?: SurfaceIconDescriptor[]; layout: SurfaceLayoutDescriptor; motion: SurfaceMotionDescriptor[]; primitives?: SurfacePrimitiveDescriptor[]; @@ -118,6 +138,7 @@ export type DriftViolationType = | "unknown-section" | "font-not-allowed" | "color-not-allowed" + | "icon-source-not-allowed" | "layout-width-exceeded" | "layout-width-undetermined" | "layout-container-missing"