Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
458a515
feat: add deterministic visual-change detection for PRs
Sep 10, 2026
b92ff21
fix: resolve design-diff theme and EMCN conventions
Sep 10, 2026
ee213d9
feat(design-diff): group binary findings by changed source
Sep 11, 2026
a44d1a4
fix(design-diff): trace presentation inputs and bound schema 3 reports
Sep 11, 2026
777a4aa
fix(design-diff): bound parser retention and preserve historical capa…
Sep 11, 2026
309054c
fix(design-diff): normalize equivalent ASTs without rewriting type bi…
Sep 11, 2026
e279fdd
fix(design-diff): trace collection writes without mixing unrelated pr…
Sep 11, 2026
18ca63a
fix(design-diff): await native Bun process exits in benchmark runner
Sep 11, 2026
691769f
fix: isolate selected async values and namespace members
Sep 11, 2026
b4f01a7
fix: bound visual evidence and finite configuration reads
Sep 11, 2026
9df1584
Merge remote-tracking branch 'origin/staging' into codex/design-diff-…
Sep 11, 2026
ed4c9f5
fix: narrow dependency propagation and deduplicate visual evidence
Sep 11, 2026
8697761
fix: trace rendered React state updates in design diff
Sep 11, 2026
e62bc4c
fix: retain captured visual inputs and reduce analysis overhead
Sep 11, 2026
c8d1bf8
perf: bound indirect evidence after visual qualification
Sep 11, 2026
e44e975
feat: exempt routine documentation from design review
Sep 11, 2026
99e7228
fix: exempt standard article metadata from design review
Sep 11, 2026
bfdf8c0
fix: restrict design notifications to authored appearance changes
Sep 11, 2026
7613e12
fix: exempt shared media metadata and orphan component removals
Sep 11, 2026
3c49d5b
fix: exclude repository artwork from designer notifications
Sep 11, 2026
7ee32ff
refactor: remove obsolete broad design detection paths
Sep 11, 2026
b3b25a5
refactor: consolidate design policy and remove obsolete analysis
Sep 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/workflows/design-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Design diff (advisory)

on:
pull_request_target:
types: [opened, reopened, synchronize, edited, ready_for_review]
branches: [staging]
workflow_dispatch:
inputs:
pull_request:
description: Open PR number targeting staging
required: true
type: number

permissions:
contents: read
pull-requests: read

concurrency:
group: design-diff-${{ github.event.pull_request.number || inputs.pull_request }}
cancel-in-progress: true

jobs:
analyze:
if: github.event_name == 'pull_request_target' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
timeout-minutes: 15
steps:
- name: Resolve immutable trusted engine and PR revisions
id: revisions
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
env:
REQUESTED_PR: ${{ inputs.pull_request }}
with:
script: |
const { owner, repo } = context.repo;
const number = context.payload.pull_request?.number ?? Number(process.env.REQUESTED_PR);
if (!Number.isSafeInteger(number) || number <= 0) throw new Error('Invalid PR number');
const pr = context.payload.pull_request ?? (await github.rest.pulls.get({ owner, repo, pull_number: number })).data;
if (pr.state !== 'open' || pr.base.ref !== 'staging' || pr.base.repo.full_name !== `${owner}/${repo}`) {
throw new Error('Expected an open PR targeting this repository staging branch');
}
const repository = (await github.rest.repos.get({ owner, repo })).data;
const engine = (await github.rest.repos.getCommit({ owner, repo, ref: repository.default_branch })).data.sha;
for (const sha of [engine, pr.base.sha, pr.head.sha]) {
if (!/^[a-f0-9]{40}$/.test(sha)) throw new Error('Invalid commit identity');
}
core.setOutput('engine', engine);
core.setOutput('base', pr.base.sha);
core.setOutput('head', pr.head.sha);
core.setOutput('pr', String(number));

- name: Checkout trusted engine only
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ steps.revisions.outputs.engine }}
fetch-depth: 0
persist-credentials: false

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.4.1

- name: Install trusted dependencies
run: bun install --frozen-lockfile --ignore-scripts

- name: Fetch PR source as Git objects
env:
BASE_SHA: ${{ steps.revisions.outputs.base }}
HEAD_SHA: ${{ steps.revisions.outputs.head }}
GH_TOKEN: ${{ github.token }}
run: |
AUTH_HEADER="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')"
git -c "http.extraheader=AUTHORIZATION: basic $AUTH_HEADER" fetch --no-tags origin "$BASE_SHA" "$HEAD_SHA" >/dev/null 2>&1
test "$(git rev-parse --verify "$BASE_SHA^{commit}")" = "$BASE_SHA"
test "$(git rev-parse --verify "$HEAD_SHA^{commit}")" = "$HEAD_SHA"

- name: Analyze source
env:
BASE_SHA: ${{ steps.revisions.outputs.base }}
HEAD_SHA: ${{ steps.revisions.outputs.head }}
DESIGN_DIFF_PR: ${{ steps.revisions.outputs.pr }}
DESIGN_DIFF_ENGINE_SHA: ${{ steps.revisions.outputs.engine }}
REPORT_PATH: ${{ runner.temp }}/design-diff-${{ steps.revisions.outputs.pr }}-${{ steps.revisions.outputs.head }}.json
run: bun run design:diff --base "$BASE_SHA" --head "$HEAD_SHA" --output "$REPORT_PATH" > /dev/null 2>&1

- name: Retain JSON report
if: ${{ always() && steps.revisions.outcome == 'success' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: design-diff-${{ steps.revisions.outputs.pr }}-${{ steps.revisions.outputs.head }}
path: ${{ runner.temp }}/design-diff-${{ steps.revisions.outputs.pr }}-${{ steps.revisions.outputs.head }}.json
retention-days: 7
if-no-files-found: error
63 changes: 42 additions & 21 deletions bun.lock

Large diffs are not rendered by default.

171 changes: 171 additions & 0 deletions design-diff.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{
"sourceRoots": ["apps/", "packages/"],
"exclude": [
"(?:^|/)(?:node_modules|__tests__|__fixtures__|fixtures|test-results|\\.source|dist|build)/",
"\\.(?:test|spec)\\.[cm]?[jt]sx?$",
"(?:^|/)next-env\\.d\\.ts$",
"(?:^|/)public/(?!.*\\.(?:woff2?|ttf|otf|eot)$)",
"(?:^|/)sandbox/bundles/"
],
"mediaModules": ["lucide-react", "react-icons", "next/image", "next/legacy/image"],
"mediaSources": [
"^(?:apps/sim/lib/og/|apps/sim/app/\\(landing\\)/og-utils\\.tsx$|apps/docs/app/api/og/)",
"(?:^|/)(?:opengraph|twitter)-image\\.[jt]sx?$",
"^apps/sim/app/\\(landing\\)/components/hero/components/(?:hero-platform-(?:loop|stage|intro)|hero-visual|hero-chat-loop)/",
"^apps/sim/app/\\(landing\\)/components/features/components/captured-platform-surface\\.tsx$",
"^apps/sim/app/\\(landing\\)/components/footer/components/footer-wordmark-loop/"
],
"mediaSymbols": [
{
"file": "/components/resource-empty-state/",
"names": [
"LogsGraphic",
"TablesGraphic",
"FilesGraphic",
"DocumentsGraphic",
"KnowledgeIsoMark",
"BoreInterior"
]
}
],
"renderedMarkdown": ["apps/docs/content/", "apps/sim/content/"],
"aliases": [
{
"from": "apps/sim/",
"prefix": "@/",
"target": "apps/sim/"
},
{
"from": "apps/docs/",
"prefix": "@/",
"target": "apps/docs/"
}
],
"themes": [
{
"roots": ["apps/sim/", "packages/emcn/", "packages/workflow-renderer/"],
"path": "apps/sim/app/_styles/globals.css"
},
{
"roots": ["apps/docs/", "packages/emcn/", "packages/workflow-renderer/"],
"path": "apps/docs/app/global.css"
}
],
"classFunctions": ["cn", "clsx", "classNames", "twMerge"],
"variantFunctions": ["cva"],
"nativeAppearance": [
"backgroundColor",
"titleBarStyle",
"titleBarOverlay",
"trafficLightPosition",
"vibrancy",
"visualEffectState",
"transparent",
"opacity",
"frame",
"roundedCorners",
"backgroundMaterial",
"width",
"height",
"minWidth",
"minHeight",
"maxWidth",
"maxHeight",
"resizable",
"fullscreen",
"autoHideMenuBar",
"icon"
],
"infrastructure": [
"(?:^|/)(?:tailwind|postcss|next|vite|electron-vite|source)\\.config\\.",
"(?:^|/)mdx-components\\.",
"(?:^|/)lib/postcss/",
"(?:^|/)lib/cn\\.ts$",
"(?:^|/)tsconfig[^/]*\\.json$"
],
"renderingDependencies": "^(?:react(?:-dom)?|next|tailwindcss|tailwind-merge|clsx|class-variance-authority|postcss|electron|framer-motion|motion|tw-animate-css|@tailwindcss/|@radix-ui/|@react-email/|fumadocs|@mdx-js/|remark-|rehype-|lucide)",
"limits": {
"fileBytes": 2097152,
"totalBytes": 268435456,
"resolutionDepth": 24,
"resolutionSteps": 5000
},
"nativeRendering": [
"apps/desktop/src/main/terminal-themes.ts",
"apps/desktop/src/main/context-menu.ts",
"apps/desktop/src/main/tray.ts"
],
"classModules": [
"clsx",
"classnames",
"tailwind-merge",
"@sim/emcn",
"@sim/emcn/lib/cn",
"@/lib/utils",
"@/lib/cn"
],
"variantModules": ["class-variance-authority"],
"mergeFontSizes": ["micro", "caption", "small", "md"],
"fileInputs": [
{
"list": "apps/docs/lib/openapi-specs.ts",
"export": "OPENAPI_SPEC_FILES",
"root": "apps/docs",
"renderer": "apps/docs/lib/openapi.ts"
}
],
"environmentAdapters": [
{
"module": "apps/sim/lib/core/config/env-capabilities.server.ts",
"export": "wireServerFallback",
"environmentModule": "apps/sim/lib/core/config/env.ts",
"environmentExport": "env",
"implementationModule": "apps/sim/lib/core/config/env-capabilities.ts",
"implementationExport": "wireFallback"
}
],
"documentationContent": {
"components": [
{
"module": "fumadocs-ui/components/callout",
"names": ["Callout"],
"contentProps": ["type", "title"]
},
{
"module": "fumadocs-ui/components/card",
"names": ["Card", "Cards"],
"contentProps": ["title", "description", "href"]
},
{
"module": "fumadocs-ui/components/steps",
"names": ["Step", "Steps"],
"contentProps": ["title"]
},
{
"module": "fumadocs-ui/components/tabs",
"names": ["Tab", "Tabs"],
"contentProps": ["items", "value"]
},
{
"module": "@/components/ui/faq",
"names": ["FAQ"],
"contentProps": ["items"]
},
{
"module": "@/components/ui/block-info-card",
"names": ["BlockInfoCard"],
"contentProps": ["type", "color"]
},
{
"module": "@/components/ui/command-table",
"names": ["CommandTable"],
"contentProps": []
},
{
"module": "@/components/ui/what-you-will-learn",
"names": ["WhatYouWillLearn"],
"contentProps": ["items"]
}
]
}
}
18 changes: 17 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@
"test:workflow-sync": "bun --no-env-file scripts/test-workflow-sync.ts",
"type-check": "turbo run type-check",
"release": "bun run scripts/create-single-release.ts",
"test:scripts": "vitest run --config scripts/vitest.config.ts"
"test:scripts": "vitest run --config scripts/vitest.config.ts",
"design:diff": "bun --no-env-file scripts/design-diff/cli.ts",
"check:design-diff-types": "tsc --noEmit --project scripts/design-diff/tsconfig.json"
},
"overrides": {
"react": "19.2.4",
Expand Down Expand Up @@ -150,9 +152,13 @@
},
"devDependencies": {
"@babel/parser": "7.29.2",
"@babel/traverse": "7.29.0",
"@babel/types": "7.29.7",
"@biomejs/biome": "2.0.6",
"@octokit/rest": "^21.0.0",
"@sim/utils": "workspace:*",
"@types/babel__traverse": "7.28.0",
"@types/node": "24.2.1",
"@types/opentype.js": "1.3.10",
"@typescript/native": "npm:typescript@^7.0.2",
"@typescript/typescript6": "^6.0.2",
Expand All @@ -165,12 +171,19 @@
"gray-matter": "4.0.3",
"husky": "9.1.7",
"json-schema-to-typescript": "15.0.4",
"jsonc-parser": "3.3.1",
"lint-staged": "16.0.0",
"opentype.js": "1.3.4",
"parse5": "7.3.0",
"postcss": "8.5.26",
"react": "19.2.4",
"remark-frontmatter": "5.0.0",
"remark-gfm": "4.0.1",
"remark-mdx": "3.1.1",
"remark-parse": "11.0.0",
"sharp": "0.35.4",
"tailwind-merge": "3.6.0",
"tailwindcss": "4.3.3",
"turbo": "2.9.14",
"unified": "11.0.5",
"unist-util-visit": "5.1.0",
Expand All @@ -189,5 +202,8 @@
"patchedDependencies": {
"@better-auth/oauth-provider@1.6.27": "patches/@better-auth%2Foauth-provider@1.6.27.patch",
"postgres@3.4.9": "patches/postgres@3.4.9.patch"
},
"imports": {
"#design-diff/*": "./scripts/design-diff/*.ts"
}
}
14 changes: 9 additions & 5 deletions scripts/check-script-test-coverage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bun
/**
* Asserts every `scripts/*.test.ts` file is collected by the scripts Vitest config.
* Asserts every root script test and nested design-diff test is collected by the scripts Vitest config.
*
* The root `test` script once chained a hand-maintained list of `test:*` entries, and a
* hand-maintained list silently drifts from the files on disk: a test added without a matching
Expand Down Expand Up @@ -57,10 +57,14 @@ const collected = new Set(
)
)

const onDisk = readdirSync(path.join(ROOT, 'scripts'))
.filter((file) => file.endsWith('.test.ts'))
.map((file) => `scripts/${file}`)
.sort()
const onDisk = [
...readdirSync(path.join(ROOT, 'scripts'))
.filter((file) => file.endsWith('.test.ts'))
.map((file) => `scripts/${file}`),
...readdirSync(path.join(ROOT, 'scripts/design-diff/tests'), { recursive: true })
.filter((file): file is string => typeof file === 'string' && file.endsWith('.test.ts'))
.map((file) => `scripts/design-diff/tests/${file.split(path.sep).join('/')}`),
].sort()

const orphaned = onDisk.filter((file) => !collected.has(file))
if (orphaned.length > 0) {
Expand Down
Loading
Loading