diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index d7bf60a75e4..4a96f73a9f9 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -57,6 +57,12 @@ All three must hold: reported yet, and treating "not failing" as "passing" reports the PR clean before CI has had its say. Wait for it — the step-10 stop condition covers a check that never settles. +A passing design-conformance CI step can still contain warnings. Read its latest report and +triage findings using `/ship`'s [committed design check](../ship/SKILL.md#committed-design-check). +Intentional system changes and justified exceptions may remain once explained in the PR; +they do not prevent a clean review or require another fix loop. Honor decisions already made +in this session. Operational failures must be resolved before reporting the PR clean. + Do not stop early on "no new comments this round" alone — a thread can be open from an earlier round, and cubic often lands its first threads a round after Greptile's. Always check all three conditions freshly after every push. @@ -85,11 +91,14 @@ conditions freshly after every push. If `mergeable` is `CONFLICTING`, fix that first (step 2). If a check is failing, fix that too — treat it exactly like a review finding. If a check is still `pending`, do not evaluate "clean" at all: go to step 9 and wait for it. Otherwise, if Greptile is 5/5, every thread - across all pages has `isResolved: true`, and every check has finished and passed, stop — + across all pages has `isResolved: true`, every check has finished and passed, and any design + warnings have been triaged as above, stop — report the outcome (see "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, merge `origin/staging`, resolve the conflicts, run the - usual pre-push checks, push, and go to step 8 to re-trigger review. + usual pre-commit checks and commit the resolution. Run `/ship`'s + [committed design check](../ship/SKILL.md#committed-design-check) against the resulting HEAD + before pushing, then go to step 8 to re-trigger review. 3. **If no review has run yet** (fresh PR, no bot comments): both run automatically on PR open — confirm via `gh pr checks ` (look for `Greptile Review` and `cubic · AI code reviewer`) and @@ -131,7 +140,11 @@ conditions freshly after every push. gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip either gate just as easily as the original commit did. -7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's +7. **Commit, check and push** the round's fixes as one commit. After committing and before + every push, follow `/ship`'s [committed design check](../ship/SKILL.md#committed-design-check), + including warning triage and committing/rechecking any resulting fixes. Push only the + checked HEAD; rerun after a rebase or any other change to the comparison. + Use `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed with no conflicts, not only the cherry-pick rebuild path; both rewrite commits already published to the remote, so a plain `git push` can be rejected either way — then run `/ship` diff --git a/.agents/skills/emcn-design-review/SKILL.md b/.agents/skills/emcn-design-review/SKILL.md index 2f14b66c83c..87238833957 100644 --- a/.agents/skills/emcn-design-review/SKILL.md +++ b/.agents/skills/emcn-design-review/SKILL.md @@ -16,6 +16,14 @@ User arguments: $ARGUMENTS This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA variants and CSS variable design tokens. All UI must use emcn components and tokens. +## Deterministic conformance and fixes + +For committed product UI changes, run `bun run check:design --base --head HEAD`. This checks central derivation, component ownership and system changes; it does not judge whether an approved red variant or monospace font is a good product choice. The style review advice below may inform suggestions, but must not be presented as additional deterministic linter rules. + +With fixes enabled, correct straightforward local violations using the cited central token, recipe or component prop. For a small gray adjustment, choose the approved token that fits its role (body, muted, icon or surface); do not hardcode the nearest hex. Ask the engineer when a correction has broad impact or ambiguous intent, especially before changing shared definitions or adding variants. An intentional change or justified artwork exception can proceed with an explanation in the PR; retain the warning. New shared standards should involve the designer. Do not enlarge this task into a whole-codebase cleanup or invent a central token just to pass. + +Untouched debt stays quiet. Bringing noncompliant styling into a new file or component is an opportunity to fix it, even when its value is unchanged. Product artwork should use the registered central libraries; central asset changes notify. Landing pages, user media and sanctioned provider branding are separate scope. Unknown HTML illustrations may still receive normal styling warnings. Unresolved inputs are not proof of conformance. Only committed blobs are checked, and exit 2 means an operational failure; CI findings currently warn without blocking. See `scripts/design-conformance/README.md` for exact coverage. + ## Steps 1. Read the emcn public barrel at `packages/emcn/src/index.ts` (re-exports components, Calendar, Table*, and icons) to know what's available; for the full icon set read `packages/emcn/src/icons/index.ts` diff --git a/.agents/skills/ship/SKILL.md b/.agents/skills/ship/SKILL.md index 6db24936127..32f0ea7c530 100644 --- a/.agents/skills/ship/SKILL.md +++ b/.agents/skills/ship/SKILL.md @@ -1,6 +1,6 @@ --- name: ship -description: Commit, push, and open a PR to staging in one shot — runs the cleanup pass and, when migrations changed, the db-migrate safety review first +description: Commit, check design conformance, push, and open a PR to staging — runs cleanup and the applicable migration safety review first argument-hint: "[optional context or scope notes]" --- @@ -72,7 +72,7 @@ When the user runs `/ship`: bun run docs-manifest:check || { echo "❌ docs manifest out of sync — do not ship"; exit 1; } ``` If Phase A regenerated a file, its matching `:check` in Phase B now passes trivially — that parity is the point. Do not ship with any generator or audit failing; fix the cause (never silence it) and re-run. `check:migrations` and `type-check` are covered by steps 5 and CI respectively and are not repeated here. -7. **Stage and commit** the changes with the generated message — including any files Phase A regenerated in step 6 +7. **Stage and commit** the changes with the generated message — including any files Phase A regenerated in step 6. Then run the [committed design check](#committed-design-check) below and resolve or explain its findings before step 8. 8. **Push to origin** using the current branch name — `--force-with-lease` if step 2's sync check did any history rewrite (a clean rebase or a cherry-pick rebuild) on a branch that had already been pushed once; a plain push would be rejected in exactly the polluted-remote case @@ -88,7 +88,31 @@ When the user runs `/ship`: positional/line-by-line comparison against the PR's oldest-first list can spuriously fail on any multi-commit branch. These two lists must describe the same commits in the same order (same subjects, the last one being the commit from step 7). If they don't match, the branch - still has a problem — redo step 2's fix and `git push --force-with-lease`. + still has a problem — redo step 2's fix, repeat the committed design check for the resulting HEAD, and `git push --force-with-lease`. + +## Committed design check + +After committing and before **every push**, run this from the repository root with Bun 1.4.1: + +```bash +bun run check:design --base origin/staging --head HEAD +``` + +Run it for every `/ship`; let the checker apply its own scope. A `.tsx`-only condition would miss CSS, Tailwind configuration, artwork and contract-registry changes. `check:audits` deliberately excludes this base-dependent command. It reads committed merge-base → HEAD blobs, so a run before committing cannot validate the pending changes. + +Interpret both the exit status and the report: + +- **0 with a completed report:** no findings; continue to push. +- **1 with a completed report:** read the usage violations and central-system notifications. Triage them before pushing; findings are warnings, not an automatic shipping failure. +- **2, unexpected termination, or no completed report:** the check did not complete. Fix the operational problem and rerun before pushing. A startup failure with exit 1 is not a findings report. Do not hide failures with `|| true` or treat missing output as a pass. + +Fix straightforward usage violations through the cited central component, prop, recipe or token. For a small local gray correction, choose the approved token appropriate to its role. Do not invent a new token or loosen a contract just to remove the warning. Keep fixes within the work being shipped; unchanged debt elsewhere can wait for its own cleanup. + +When a change has broad shared impact or ambiguous intent, explain the finding and ask the engineer how to proceed. Intentional central-system changes and justified exceptions can proceed with an explanation in the PR; involve the designer for new standards or ambiguous broad changes. Honor decisions already given in this session. Retain the warning rather than weakening the linter or requiring every intended system change to produce a clean report. + +If this review produces edits, rerun the affected generation/lint/audit checks from step 6, commit the fixes, then repeat the design check against the new HEAD. Also rerun after a rebase, conflict resolution or other change to the comparison. Push only the checked commit; uncommitted fixes are not covered by an earlier result. + +In the PR's **Testing** section, record the design-check outcome and explain any retained warnings. Leave **No new warnings introduced** unchecked when warnings remain. This review is about central design-system conformance; approved component variants, colours and fonts remain available for the engineer's product decisions. See `scripts/design-conformance/README.md` for scope and known unchecked inputs. ## Commit Message Format @@ -137,7 +161,7 @@ Tested manually (or describe testing) - [x] Code follows project style guidelines - [x] Self-reviewed my changes - [ ] Tests added/updated and passing -- [x] No new warnings introduced +- [ ] No new warnings introduced - [x] I confirm that I have read and agree to the terms outlined in the [Contributor License Agreement (CLA)](./CONTRIBUTING.md#contributor-license-agreement-cla) ``` diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index ab28f35e8a5..3f8ece5f809 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -201,18 +201,11 @@ jobs: timeout-minutes: 15 steps: - # The diff-based audits below need a base commit to read, and the default - # depth of 1 clones a single commit with no parent. They normally fetch - # their base by SHA (see "Resolve base ref"), so this depth only covers the - # `HEAD~1` fallback — but without it that fallback resolves to nothing. - # - # Worth stating because the failure was invisible for so long: the migration - # audit read the resulting `git diff` failure as "no migrations changed" and - # exited 0, so it had never actually run on a push build. + # Design conformance compares immutable merge-base sources and requires full history. - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: - fetch-depth: 2 + fetch-depth: 0 - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 @@ -301,37 +294,11 @@ jobs: echo "✅ All env flags are properly configured" - # One fetch for both base-ref audits, and no `|| true`: a swallowed fetch leaves - # the base ref absent, which neither audit can tell apart from a branch that - # changed nothing. The block-registry check at least degrades to a visible - # `⚠ … skipping` line; the migration audit printed `✓ No new migrations to - # check` and exited 0, clearing the only guard on production DDL. - # - # Depth stays at 1 — without a merge-base the migration audit diffs the two - # tips, which under `--diff-filter=AM` is exactly the migrations new here. - # Resolved once for both diff-based audits, and never with `|| true`: a - # swallowed fetch leaves the base absent, which neither audit can tell apart - # from a branch that changed nothing. - # - # On push the base is `github.event.before`, the tip the branch had before - # this push — not `HEAD~1`, which names only the last commit and would let a - # multi-commit push slip every earlier commit's migrations past the audit. - # It is fetched by SHA at depth 1; the audits diff two tips and need no - # common ancestry. An all-zero `before` means the branch is new and has no - # predecessor to diff, so `HEAD~1` remains the fallback there. + # Resolve immutable event revisions without making the checkout shallow again. + # The same base covers the complete push for migration and block-registry checks. - name: Resolve base ref for diff-based audits id: audit_base - run: | - if [ "${{ github.event_name }}" = "pull_request" ]; then - git fetch --depth=1 origin "${{ github.base_ref }}" - echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" - elif [ -n "${{ github.event.before }}" ] && - [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then - git fetch --depth=1 origin "${{ github.event.before }}" - echo "ref=${{ github.event.before }}" >> "$GITHUB_OUTPUT" - else - echo "ref=HEAD~1" >> "$GITHUB_OUTPUT" - fi + run: bun --no-env-file scripts/design-conformance/ci.ts --resolve-refs - name: Check block registry invariants run: bun run apps/sim/scripts/check-block-registry.ts "${{ steps.audit_base.outputs.ref }}" @@ -348,6 +315,13 @@ jobs: - name: Verify docs manifest is in sync run: bun run docs-manifest:check + # Only findings (exit 1) are non-blocking; operational errors still fail CI. + - name: Design conformance (warnings) + env: + DESIGN_BASE: ${{ steps.audit_base.outputs.ref }} + DESIGN_HEAD: ${{ steps.audit_base.outputs.head }} + run: bun --no-env-file scripts/design-conformance/ci.ts --base "$DESIGN_BASE" --head "$DESIGN_HEAD" + - name: Migration safety (zero-downtime) audit run: bun run check:migrations "${{ steps.audit_base.outputs.ref }}" @@ -358,7 +332,7 @@ jobs: # excludes *.test.ts(x), and including them today surfaces ~2.2k errors, # so that is its own cleanup rather than a gate to switch on here. - name: Type-check all workspaces - run: bunx turbo run type-check + run: bun run type-check # cloud-review-tools.test.ts runs the real helper on the runner, which shells # out to rg. Blacksmith's image ships it, GitHub's doesn't. diff --git a/CLAUDE.md b/CLAUDE.md index b0f73d13053..2f76a2455cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,11 +4,12 @@ You are a professional software engineer. All code must follow best practices: a ## Global Standards +- **Design conformance**: Before pushing committed product UI changes, run `bun run check:design --base --head HEAD` (for example, `origin/staging`). This prevents drift from central EMCN components, tokens and approved artwork; approved combinations remain the engineer's choice. Fix routine violations through the cited central mechanism, including choosing a suitable approved gray for a small local correction. Ask the engineer when intent is unclear or a fix changes shared tokens, recipes or variants. Intentional system changes and justified exceptions may proceed with an explanation in the PR; keep the warning visible and involve the designer for new standards or broad ambiguous changes. Added, changed or extracted noncompliant styling needs correction; untouched debt can wait for the separate cleanup pass. CI findings are warnings; exit 2 is an operational failure. Only committed blobs are checked; see `scripts/design-conformance/README.md` for scope and unchecked inputs. - **Linting / Audit**: `bun run check:api-validation` must pass on PRs. Do not introduce route-local boundary Zod schemas, direct route Zod imports, or ad-hoc client wire types — see "API Contracts" and "API Route Pattern" below - **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`. Inside API routes wrapped with `withRouteHandler`, loggers automatically include the request ID — no manual `withMetadata({ requestId })` needed - **API Route Handlers**: All API route handlers (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) must run inside `withRouteHandler`. Ordinary internal and v2 handlers use the shared JSON/binary route builders, which already apply it; never double-wrap a builder. Use raw `withRouteHandler` only for documented protocol or lifecycle exceptions. See "API Route Pattern" below - **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments -- **Styling**: Never update global styles. Keep all styling local to components +- **Styling**: Derive product styling from central tokens, component props and recipes. Keep product composition local. Change central definitions only when intentionally changing the shared standard, and explain that change; never add a global token or override solely to hide a local conformance warning. Landing-page presentation is exempt from the product conformance policy. - **ID Generation**: Never use `crypto.randomUUID()`, `nanoid`, or `uuid` package. Use `generateId()` (UUID v4) or `generateShortId()` (compact) from `@sim/utils/id` - **Common Utilities**: Use shared helpers from `@sim/utils` instead of inline implementations: - `sleep(ms)` from `@sim/utils/helpers` — never `new Promise(resolve => setTimeout(resolve, ms))` diff --git a/biome.json b/biome.json index 81e02110a68..8b506b4a531 100644 --- a/biome.json +++ b/biome.json @@ -1,6 +1,10 @@ { "$schema": "https://biomejs.dev/schemas/2.0.6/schema.json", - "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": false }, + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": false + }, "files": { "ignoreUnknown": false, "includes": [ @@ -143,8 +147,12 @@ "overrides": [ { "includes": ["apps/sim/app/_styles/tailwind.css"], - "linter": { "enabled": false }, - "formatter": { "enabled": false } + "linter": { + "enabled": false + }, + "formatter": { + "enabled": false + } }, { "includes": ["packages/**"], @@ -156,6 +164,15 @@ } } } + }, + { + "includes": [ + "scripts/design-conformance/catalogue.json", + "scripts/design-conformance/contracts.json" + ], + "formatter": { + "enabled": false + } } ], "javascript": { diff --git a/bun.lock b/bun.lock index 52861a831d2..641b28c4efe 100644 --- a/bun.lock +++ b/bun.lock @@ -6,9 +6,14 @@ "name": "simstudio", "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/bun": "1.4.1", + "@types/node": "24.2.1", "@types/opentype.js": "1.3.10", "@typescript/native": "npm:typescript@^7.0.2", "@typescript/typescript6": "^6.0.2", @@ -23,10 +28,14 @@ "json-schema-to-typescript": "15.0.4", "lint-staged": "16.0.0", "opentype.js": "1.3.4", + "parse5": "7.3.0", + "postcss": "8.5.26", + "postcss-value-parser": "4.2.0", "react": "19.2.4", "remark-gfm": "4.0.1", "remark-parse": "11.0.0", "sharp": "0.35.4", + "tailwindcss": "4.3.3", "turbo": "2.9.14", "unified": "11.0.5", "unist-util-visit": "5.1.0", @@ -1984,8 +1993,6 @@ "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.7.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-nZyWTmSpJEXl6VtWVMBJve/7x12DZu6sIX1z1a+ZMaHlQQRs9Zpu6NbTe/gmxYXVRpkjxyDYpZ5gx2IM6f/Wkw=="], - "@smithy/hash-node": ["@smithy/hash-node@4.4.0", "", { "dependencies": { "@smithy/core": "^3.25.0", "tslib": "^2.6.2" } }, "sha512-MkyiJfdnDlBdmq26Cxskw2dtX6V/EgTjCriPc7Gq0084hncjIFVJ26IwHpauXJT2w79B4umF0erKi4epBR/WDA=="], - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@smithy/middleware-compression": ["@smithy/middleware-compression@4.6.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "fflate": "0.8.3", "tslib": "^2.6.2" } }, "sha512-Q9d+luiRjyHT6kCL/9NyGpdZJgodh4vtvfHC6H8SqoVKrV2k9RoyI9/IloVfdCYks3/2DIi7BsYYLcda5KZS0A=="], @@ -2160,10 +2167,14 @@ "@types/archiver": ["@types/archiver@8.0.0", "", { "dependencies": { "@types/node": "*", "@types/readdir-glob": "*" } }, "sha512-YpXPbEuv9+eUIPPQWUPahj3cvs9isWRuF+J4z+KbdYVDO3rWorWQFxUVHnwPu2AgKwvgpki5F2VMX0Xx+mX45A=="], + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + "@types/braces": ["@types/braces@3.0.5", "", {}, "sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w=="], "@types/buffer-from": ["@types/buffer-from@1.1.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-2lq4YC9uLUMGHkl2IDtX4tCXSo2+hwMpOJcY1qiIk1kybc31rIlPyM1HCVJhkPFIo75a/pOVxqyvwuf5TpCG/w=="], + "@types/bun": ["@types/bun@1.4.1", "", { "dependencies": { "bun-types": "1.4.1" } }, "sha512-0AVGiTXGajf1rgKom3N+c5L7CBxuoyyv1i44M0nX4UDK0G/fnRAMiri93nHuVPIb429KKtAgj7HatVmmOjeQLA=="], + "@types/busboy": ["@types/busboy@1.5.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw=="], "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], @@ -2580,6 +2591,8 @@ "builder-util-runtime": ["builder-util-runtime@9.7.0", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw=="], + "bun-types": ["bun-types@1.4.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-loKuVrAFZKfEv+JvWkHRS9GW5IqLuLRjVXN9p+vZvBN86O5hf/pBZQ5hSoyipsrMmWObZBDvWnlmKvjKTM0PdA=="], + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="], @@ -3006,7 +3019,7 @@ "ensure-posix-path": ["ensure-posix-path@1.1.1", "", {}, "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw=="], - "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], @@ -3454,7 +3467,7 @@ "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="], - "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], @@ -3608,7 +3621,7 @@ "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], - "lucide-react": ["lucide-react@0.511.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w=="], + "lucide-react": ["lucide-react@1.23.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -4800,8 +4813,6 @@ "@azure/arm-containerinstance/@azure/abort-controller": ["@azure/abort-controller@1.1.0", "", { "dependencies": { "tslib": "^2.2.0" } }, "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw=="], - "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "@babel/generator/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], "@babel/template/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], @@ -4982,6 +4993,8 @@ "@react-email/components/@react-email/render": ["@react-email/render@2.0.6", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-xOzaYkH3jLZKqN5MqrTXYnmqBYUnZSVbkxdb5PGGmDcK6sKDVMliaDiSwfXajRC9JtSHTcGc2tmGLHWuCgVpog=="], + "@react-email/render/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "@react-email/tailwind/tailwindcss": ["tailwindcss@4.3.1", "", {}, "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q=="], "@redis/client/cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], @@ -4996,8 +5009,6 @@ "@smithy/eventstream-codec/@smithy/core": ["@smithy/core@3.25.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-TTD6el7tvKyafkXBf7XO3jLOE+qVxOTrLjp/fEGiV3BMfUHK/LfdYlQO9YgZvzxC7kqA3H/IhJXNqQgnbgjb7A=="], - "@smithy/hash-node/@smithy/core": ["@smithy/core@3.25.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-TTD6el7tvKyafkXBf7XO3jLOE+qVxOTrLjp/fEGiV3BMfUHK/LfdYlQO9YgZvzxC7kqA3H/IhJXNqQgnbgjb7A=="], - "@smithy/middleware-compression/fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], "@smithy/signature-v4/@smithy/core": ["@smithy/core@3.25.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.15.0", "tslib": "^2.6.2" } }, "sha512-TTD6el7tvKyafkXBf7XO3jLOE+qVxOTrLjp/fEGiV3BMfUHK/LfdYlQO9YgZvzxC7kqA3H/IhJXNqQgnbgjb7A=="], @@ -5138,6 +5149,8 @@ "artillery/socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="], + "ast-v8-to-istanbul/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + "async-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="], "axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], @@ -5156,6 +5169,8 @@ "builder-util/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + "bun-types/@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="], + "c12/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "c12/confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], @@ -5208,6 +5223,8 @@ "docx/nanoid": ["nanoid@5.1.16", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ=="], + "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "dot-prop/type-fest": ["type-fest@5.8.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA=="], "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], @@ -5274,8 +5291,6 @@ "fumadocs-openapi/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - "fumadocs-openapi/lucide-react": ["lucide-react@1.23.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw=="], - "fumadocs-ui/@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA=="], "fumadocs-ui/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.17", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.13", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.10", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.12", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw=="], @@ -5290,8 +5305,6 @@ "fumadocs-ui/@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.6", "@radix-ui/react-roving-focus": "1.1.13", "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg=="], - "fumadocs-ui/lucide-react": ["lucide-react@1.23.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw=="], - "gaxios/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], "gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], @@ -5350,8 +5363,6 @@ "log-update/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], - "loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - "magicast/@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], "make-dir/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], @@ -5380,8 +5391,6 @@ "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "pdf-lib/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "pino-pretty/pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], @@ -5656,8 +5665,6 @@ "@smithy/eventstream-codec/@smithy/core/@smithy/types": ["@smithy/types@4.15.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg=="], - "@smithy/hash-node/@smithy/core/@smithy/types": ["@smithy/types@4.15.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg=="], - "@smithy/util-utf8/@smithy/core/@smithy/types": ["@smithy/types@4.15.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg=="], "@trigger.dev/core/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.41.1", "", {}, "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA=="], @@ -5740,6 +5747,8 @@ "builder-util/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "bun-types/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + "c12/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "chrome-launcher/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], @@ -6016,6 +6025,8 @@ "gtoken/gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "html-to-text/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "imapflow/pino/pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], "imapflow/pino/thread-stream": ["thread-stream@4.2.0", "", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="], diff --git a/package.json b/package.json index ca71919251a..75df9698bbd 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,13 @@ "test": "bun run test:setup && bun run test:scripts && turbo run test", "test:setup": "bun run --cwd packages/sim-setup test", "test:search-performance": "KNOWLEDGE_SEARCH_PERFORMANCE_TEST=true bun --no-env-file scripts/test-knowledge-acls.ts search-latency", - "format": "turbo run format", - "format:check": "turbo run format:check", - "lint": "turbo run lint", - "lint:check": "turbo run lint:check", + "format": "biome format --write scripts/design-conformance scripts/check-design-conformance*.ts && turbo run format", + "format:check": "biome format scripts/design-conformance scripts/check-design-conformance*.ts && turbo run format:check", + "lint": "biome check --write scripts/design-conformance scripts/check-design-conformance*.ts && turbo run lint", + "lint:check": "biome check scripts/design-conformance scripts/check-design-conformance*.ts && turbo run lint:check", "lint:helm": "helm lint helm/sim --strict --values helm/sim/ci/default-values.yaml", - "lint:all": "turbo run lint && bun run lint:helm", - "check": "turbo run format:check", + "lint:all": "bun run lint && bun run lint:helm", + "check": "bun run format:check", "check:egress-boundary": "bun run scripts/check-egress-boundary.ts", "check:boundaries": "bun run scripts/check-monorepo-boundaries.ts", "check:api-validation": "bun run scripts/check-api-validation-contracts.ts --check", @@ -113,9 +113,11 @@ "agent-stream-docs:check": "bun run scripts/sync-agent-stream-docs.ts --check", "prepare": "bun husky", "test:workflow-sync": "bun --no-env-file scripts/test-workflow-sync.ts", - "type-check": "turbo run type-check", + "type-check": "bun run type-check:design && 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", + "check:design": "bun --no-env-file scripts/check-design-conformance.ts", + "type-check:design": "tsc --noEmit -p scripts/design-conformance/tsconfig.json" }, "overrides": { "react": "19.2.4", @@ -151,9 +153,14 @@ }, "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/bun": "1.4.1", + "@types/node": "24.2.1", "@types/opentype.js": "1.3.10", "@typescript/native": "npm:typescript@^7.0.2", "@typescript/typescript6": "^6.0.2", @@ -168,10 +175,14 @@ "json-schema-to-typescript": "15.0.4", "lint-staged": "16.0.0", "opentype.js": "1.3.4", + "parse5": "7.3.0", + "postcss": "8.5.26", + "postcss-value-parser": "4.2.0", "react": "19.2.4", "remark-gfm": "4.0.1", "remark-parse": "11.0.0", "sharp": "0.35.4", + "tailwindcss": "4.3.3", "turbo": "2.9.14", "unified": "11.0.5", "unist-util-visit": "5.1.0", @@ -190,5 +201,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-conformance/*": "./scripts/design-conformance/*.ts" } } diff --git a/packages/emcn/src/AGENTS.md b/packages/emcn/src/AGENTS.md index c7bfda44b81..e8f60f0a68a 100644 --- a/packages/emcn/src/AGENTS.md +++ b/packages/emcn/src/AGENTS.md @@ -2,10 +2,12 @@ These rules apply to `packages/emcn/**`. -- Import from `@sim/emcn`, never from subpaths except CSS files. +- Import components and tokens from `@sim/emcn`; icons use `@sim/emcn/icons`, and CSS files may use their file paths. - Use Radix UI primitives for accessibility where applicable. - Use CVA when a component has 2+ variants; use direct `className` composition for single-style components. - Export both the component and its variants helper when using CVA. - Keep tokens consistent with the chip-pill canonical look: normal font-weight, `--text-body` value text, `--text-icon` icons at `size-[14px]`, `rounded-lg`. Components own their exact geometry tokens (e.g. `Button` uses `rounded-[5px]`), but never their own font-weight — every primitive inherits the document 400, and a weight class is reached for only to step deliberately up. See `.claude/rules/emcn-components.md` for the full chip-chrome reference. - Prefer `transition-colors` for interactive hover and active states. - Use TSDoc when documenting public components or APIs. + +Central component, recipe and icon changes are design-system changes, including intentional improvements. Run `bun run check:design --base --head HEAD` from the repository root after committing. Explain intended shared changes in the PR. Agents may fix straightforward consumer token mistakes; ask the engineer when shared impact or intent is ambiguous, and involve the designer for new standards. Keep resulting review warnings visible. See `scripts/design-conformance/README.md`. diff --git a/scripts/check-design-conformance-appearance.test.ts b/scripts/check-design-conformance-appearance.test.ts new file mode 100644 index 00000000000..fc01e8e4b53 --- /dev/null +++ b/scripts/check-design-conformance-appearance.test.ts @@ -0,0 +1,418 @@ +/** biome-ignore-all lint/suspicious/noTemplateCurlyInString: Fixtures contain literal proposed JavaScript templates. */ +import { existsSync } from 'node:fs' +import { beforeAll, expect, test } from 'vitest' +import { extract } from '#design-conformance/extract' +import { loadCatalogue } from '#design-conformance/io' +import { Linter } from '#design-conformance/lint' +import { TOKEN_FILE } from '#design-conformance/model' + +let linter: Linter +beforeAll(async () => { + const c = loadCatalogue() + linter = await Linter.create(c.catalogue, c.hash) +}) +const ui = 'apps/sim/components/example.tsx' +const commits = { base: 'a'.repeat(40), head: 'b'.repeat(40), mergeBase: 'a'.repeat(40) } +function diff(before: string, after: string, file = ui, oldFile = file) { + return linter.analyze( + [ + { + status: 'M', + before: { path: oldFile, blob: 'a'.repeat(40), mode: '100644' }, + after: { path: file, blob: 'b'.repeat(40), mode: '100644' }, + }, + ], + (e) => (e.blob.startsWith('a') ? before : after), + commits + ) +} +const element = (classes: string) => + `const A=()=> ` +const shared = (props = '', body = '') => + `import {ChipInput, Chip, DropdownMenuItem, Button} from '@sim/emcn'; const A=()=> <>${body}` + +for (const [category, before, after] of [ + ['colours', 'text-[var(--text-body)]', 'text-[var(--text-muted)]'], + ['spacing', 'p-2', 'p-4'], + ['dimensions', 'w-4', 'w-6'], + ['typography', 'font-normal text-small', 'font-medium text-caption'], + ['radii', 'rounded-lg', 'rounded-none'], + ['borders', 'border', 'border-2'], + ['effects', 'opacity-50', 'opacity-100'], + ['layout', 'flex-nowrap', 'flex-wrap'], + ['visibility', 'overflow-visible', 'overflow-hidden'], + ['layering', 'z-10', 'z-20'], + ['motion', 'duration-150', 'duration-200'], +] as const) { + test(`${category}: changes flag, identical styling and copy edits pass`, () => { + const report = diff(element(before), element(after)) + expect(report.flagged).toBe(true) + expect(report.findings.some((f) => f.category === category)).toBe(true) + expect( + diff(element(before), `// comment\n${element(before).replace('Text', 'Other wording')}`) + .flagged + ).toBe(false) + }) +} +test('numeric membership is irrelevant; approved new custom styling flags', () => { + expect(diff('', element('p-2')).flagged).toBe(true) + expect(diff(element('p-[773px]'), element('p-[773px]')).flagged).toBe(false) +}) +test('shared reuse including supported props and outer width passes', () => { + expect(diff('', shared('className="w-full min-w-0 flex-1"')).flagged).toBe(false) + expect(diff('', shared('', 'New action')).flagged).toBe(false) + expect(diff('', shared('', 'Another action')).flagged).toBe(false) +}) +test('shared appearance overrides flag, including already approved values', () => { + expect(diff(shared(), shared('className="h-[30px] rounded-lg"')).flagged).toBe(true) + expect(diff('', shared('className="bg-[var(--surface-4)]"')).findings[0].rule).toBe( + 'shared-component-override' + ) +}) +test('changing a control variant or size flags, disabled business logic does not', () => { + const a = shared('', '') + expect(diff(a, a.replace('primary', 'destructive')).flagged).toBe(true) + expect(diff(a, a.replace('busy', 'loading')).flagged).toBe(false) + expect(diff(shared('minWidth={240}'), shared('minWidth={220}')).flagged).toBe(true) +}) +test('adding repeated menu options and deleting controls passes', () => { + const item = 'One' + expect(diff(shared('', item), shared('', item + item.replace('One', 'Two'))).flagged).toBe(false) + expect(diff(element('p-2'), '').flagged).toBe(false) +}) +test('hover, theme and responsive context changes are detected', () => { + for (const after of ['focus:p-2', 'dark:hover:p-2', 'md:hover:p-2']) + expect(diff(element('hover:p-2'), element(after)).flagged).toBe(true) +}) +test('conditional styling retains branches without interpreting business predicates', () => { + const a = "const A=()=>
" + expect(diff(a, a.replace('busy', 'loading')).flagged).toBe(false) + expect(diff(a, "const A=()=>
").flagged).toBe(true) +}) +test('CVA defaults, variant mapping and compound conditions are detected', () => { + const a = + "import {cva} from 'class-variance-authority'; export const control=cva('p-2',{variants:{size:{small:'p-1',large:'p-4'}},defaultVariants:{size:'small'},compoundVariants:[{size:'small',className:'rounded-lg'}]})" + expect( + diff(a, a.replace("size:'small'},", "size:'large'},")).findings.some( + (f) => f.rule === 'variant-default-changed' + ) + ).toBe(true) + expect(diff(a, a.replace("small:'p-1'", "small:'p-2'")).flagged).toBe(true) + expect(diff(a, a.replace("[{size:'small'", "[{size:'large'")).flagged).toBe(true) +}) +test('composition precedence and important modifiers are preserved', () => { + const a = "import {cn} from '@sim/emcn'; const A=()=>
" + expect( + diff(a, a.replace("'p-2','p-4'", "'p-4','p-2'")).findings.some( + (f) => f.rule === 'style-precedence-changed' + ) + ).toBe(true) + expect(diff(element('p-2'), element('!p-2')).flagged).toBe(true) + expect(diff(element('p-2 text-white'), element('text-white p-2')).flagged).toBe(false) +}) +test('local renames and constant extraction are equivalent', () => { + const a = element('p-2') + expect( + diff(a, "const PAD='p-2'; const Renamed=()=> ") + .flagged + ).toBe(false) + expect(diff(a, "const PAD='p-4'; const Renamed=()=> ', + '', + 'apps/sim/components/a.html' + ).flagged + ).toBe(true) +}) +test('central tokens and declarative theme infrastructure flag semantically', () => { + expect(diff(':root{--x:8px}', ':root{--x:16px}', TOKEN_FILE).flagged).toBe(true) + expect(diff(':root{--x:#abc}', ':root {/*comment*/ --x: #ABC;}', TOKEN_FILE).flagged).toBe(false) + expect( + diff( + '@custom-variant dark (&:where(.dark *));', + '@custom-variant dark (&:where(.night *));', + TOKEN_FILE + ).flagged + ).toBe(true) +}) +test('uncertainty, media and backend expressions do not notify', () => { + const a = "import {style} from './helper'; const A=()=>
" + const r = diff(a, a.replace('data', 'otherData')) + expect(r.flagged).toBe(false) + expect(r.unchecked.length).toBeGreaterThan(0) + expect( + diff( + '', + "const data={colour:'#123',padding:777}; sql`select 1`; telemetry.setAttribute('name','p-4')" + ).flagged + ).toBe(false) + expect(diff('', '').flagged).toBe(false) +}) +test('parser errors remain unchecked and cannot execute source', async () => { + expect(diff('', 'const A=()=>
{ + expect(diff('.x{padding:8px}', '.x{', 'apps/sim/components/a.css').flagged).toBe(false) + const r = diff(element('p-2'), 'const A=()=> ` + expect( + diff(a, a.replace('span', 'div').replace('/span', '/div').replace('h-4 w-4', 'h-6 w-6')).flagged + ).toBe(false) + for (const classes of ['p-4', 'p-2 bg-red-500', 'p-2 h-8']) + expect(diff(a, a.replace('className="p-2"', `className="${classes}"`)).flagged).toBe(true) + expect(diff('', 'const A=()=> {false}').flagged).toBe(true) + expect(diff('', 'const A=()=> Label').flagged).toBe(true) + expect(diff('', 'const A=()=> ').flagged).toBe( + true + ) +}) +test('conditional icons, native favicon wrappers and established loaders pass', () => { + const a = `import {Globe, Terminal} from '@sim/emcn/icons'; import {ThinkingLoader} from '@/components/ui'; const Choice=active?Globe:Terminal; const A=()=> <>{url?:}{busy?:}` + expect(diff('', a).flagged).toBe(false) + expect( + diff( + '', + `import {ThinkingLoader} from '@/components/ui'; const A=()=>
` + ).flagged + ).toBe(true) + expect( + diff( + '', + `function IconPanel(){return
Settings
} const A=()=> ` + ).flagged + ).toBe(true) + expect( + diff( + '', + `import {ThinkingLoader} from './custom'; const A=()=> ` + ).flagged + ).toBe(true) +}) +test('established icon slots accept runtime selection without exempting text or controls', () => { + const a = `import {SettingsResourceRow} from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'; const Choice=choose(data); const A=()=> } title="Server"/>` + expect(diff('', a).flagged).toBe(false) + expect( + diff( + '', + a.replace('', '') + ).flagged + ).toBe(true) + expect( + diff('', a.replace('', 'Label')) + .flagged + ).toBe(true) + expect( + diff( + '', + a.replace('settings/components/settings-resource-row', 'settings/components/custom-row') + ).flagged + ).toBe(true) +}) +test('unchanged forwarded classes allow literal removals; changed computation stays unchecked', () => { + const a = `const A=({className})=>
` + const r = diff(a, a.replace(' shadow-lg', '')) + expect(r.findings.some((f) => f.property === 'box-shadow' && f.value === '[]')).toBe(true) + expect(r.findings[0].reason).toContain('Explicit styling input') + expect(diff(a, a.replace("cn('p-2 shadow-lg',className)", 'computedClass(data)')).flagged).toBe( + false + ) + expect( + diff(a, a.replace(' shadow-lg', '').replace(',className)', ',otherClasses)')).flagged + ).toBe(false) + expect( + diff(a, a.replace(' shadow-lg', '').replace("'p-2',className", "className,'p-2'")).flagged + ).toBe(false) + const template = 'const A=()=>
' + expect(diff(template, template.replace(' shadow-lg', '')).flagged).toBe(true) +}) +test('unknown inputs identify their channel and property, without suppressing unrelated removals', () => { + const a = `const A=()=>
` + expect( + diff(a, a.replace("boxShadow:'0 1px 2px black',", '').replace('tint', 'newTint')).flagged + ).toBe(true) + expect(diff(a, a.replace("boxShadow:'0 1px 2px black'", 'boxShadow:shadow(data)')).flagged).toBe( + false + ) + const f = extract(a, ui) + expect(f.surfaces?.[0].unresolved?.[0]).toMatchObject({ channel: 'style', property: 'color' }) + const spread = `const A=()=>