From ec2595f654c715cb516a1aeabd477ff6a6a3633a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 10 Sep 2026 17:36:40 -0700 Subject: [PATCH 1/4] chore(repo): remove devcontainer, renovate config, and drained cache-cleanup workflow - Delete .devcontainer/ and its CONTRIBUTING section; the Dev Containers setup was undocumented elsewhere and no longer maintained - Delete renovate.json: Renovate has never opened a PR or dashboard issue on this repo; Dependabot security updates are what actually run - Delete ci-cache-cleanup.yml: it drained legacy Next.js build-cache sticky disks left by PRs open before #6080 (2026-07-30); Blacksmith evicts sticky disks after 7 days idle, so the backlog is gone and the job is a no-op - Move vitest.scripts.config.ts to scripts/vitest.config.ts with a pinned root so the repo root holds no test config; update test:scripts and check:script-tests - Drop the duplicate husky `prepare` in apps/sim; the root prepare installs the hooks --- .devcontainer/Dockerfile | 47 --------- .devcontainer/README.md | 75 -------------- .devcontainer/devcontainer.json | 37 ------- .devcontainer/docker-compose.yml | 99 ------------------ .devcontainer/post-create.sh | 138 ------------------------- .devcontainer/sim-commands.sh | 42 -------- .dockerignore | 1 - .github/CONTRIBUTING.md | 39 +------ .github/workflows/ci-cache-cleanup.yml | 39 ------- apps/sim/package.json | 1 - package.json | 2 +- renovate.json | 80 -------------- scripts/check-script-test-coverage.ts | 10 +- scripts/vitest.config.ts | 21 ++++ vitest.scripts.config.ts | 18 ---- 15 files changed, 29 insertions(+), 620 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/README.md delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/docker-compose.yml delete mode 100755 .devcontainer/post-create.sh delete mode 100755 .devcontainer/sim-commands.sh delete mode 100644 .github/workflows/ci-cache-cleanup.yml delete mode 100644 renovate.json create mode 100644 scripts/vitest.config.ts delete mode 100644 vitest.scripts.config.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index f99d9bbcba9..00000000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -FROM oven/bun:1.4.1-alpine - -# Install necessary packages for development -RUN apk add --no-cache \ - git \ - curl \ - wget \ - jq \ - sudo \ - postgresql-client \ - vim \ - nano \ - bash \ - bash-completion \ - zsh \ - zsh-vcs \ - ca-certificates \ - shadow - -# Create a non-root user with matching UID/GID -ARG USERNAME=bun -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -# Create user group if it doesn't exist -RUN if ! getent group $USER_GID >/dev/null; then \ - addgroup -g $USER_GID $USERNAME; \ - fi - -# Create user if it doesn't exist -RUN if ! getent passwd $USER_UID >/dev/null; then \ - adduser -D -u $USER_UID -G $(getent group $USER_GID | cut -d: -f1) $USERNAME; \ - fi - -# Add sudo support -RUN echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -# Set up shell environment -RUN echo "export PATH=\$PATH:/home/$USERNAME/.bun/bin" >> /etc/profile - -WORKDIR /workspace - -# Expose the ports we're interested in -EXPOSE 3000 -EXPOSE 3001 -EXPOSE 3002 \ No newline at end of file diff --git a/.devcontainer/README.md b/.devcontainer/README.md deleted file mode 100644 index 10e684d3d81..00000000000 --- a/.devcontainer/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Sim Development Container - -Development container configuration for VS Code Dev Containers and GitHub Codespaces. - -## Prerequisites - -- Visual Studio Code -- Docker Desktop or Podman Desktop -- VS Code Dev Containers extension - -## Getting Started - -1. Open this project in VS Code -2. Click "Reopen in Container" when prompted (or press `F1` β†’ "Dev Containers: Reopen in Container") -3. Wait for the container to build and initialize -4. Start developing with `sim-start` - -The setup script will automatically install dependencies and run migrations. - -## Development Commands - -### Running Services - -You have two options for running the development environment: - -**Option 1: Run everything together (recommended for most development)** -```bash -sim-start # Runs both app and socket server using concurrently -``` - -**Option 2: Run services separately (useful for debugging individual services)** -- In the **app** container terminal: `sim-app` (starts Next.js app on port 3000) -- In the **realtime** container terminal: `sim-sockets` (starts socket server on port 3002) - -### Other Commands - -- `sim-migrate` - Push schema changes to the database -- `sim-generate` - Generate new migrations -- `build` - Build the application -- `pgc` - Connect to PostgreSQL database - -## Troubleshooting - -**Build errors**: Rebuild the container with `F1` β†’ "Dev Containers: Rebuild Container" - -**Port conflicts**: Ensure ports 3000, 3002, and 5432 are available - -**Container runtime issues**: Verify Docker Desktop or Podman Desktop is running - -## Technical Details - -Services: -- **App container** (8GB memory limit) - Main Next.js application -- **Realtime container** (4GB memory limit) - Socket.io server for real-time features -- **Database** - PostgreSQL with pgvector extension -- **Migrations** - Runs automatically on container creation - -You can develop with services running together or independently. - -### Personalization - -**Project commands** (`sim-start`, `sim-app`, etc.) are automatically available via `/workspace/.devcontainer/sim-commands.sh`. - -**Personal shell customization** (aliases, prompts, etc.) should use VS Code's dotfiles feature: -1. Create a dotfiles repository (e.g., `github.com/youruser/dotfiles`) -2. Add your `.bashrc`, `.zshrc`, or other configs -3. Configure in VS Code Settings: - ```json - { - "dotfiles.repository": "youruser/dotfiles", - "dotfiles.installCommand": "install.sh" - } - ``` - -This separates project-specific commands from personal preferences, following VS Code best practices. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 99bd1b3a4fb..00000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "Sim Dev Environment", - "dockerComposeFile": "docker-compose.yml", - "service": "app", - "workspaceFolder": "/workspace", - "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", - - "customizations": { - "vscode": { - "settings": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll.biome": "explicit", - "source.organizeImports.biome": "explicit" - }, - "terminal.integrated.shellIntegration.enabled": true - }, - "extensions": [ - "biomejs.biome", - "bradlc.vscode-tailwindcss", - "ms-vscode.vscode-typescript-next", - "github.copilot", - "github.copilot-chat", - "mikestead.dotenv", - "dsznajder.es7-react-js-snippets", - "steoates.autoimport", - "oven.bun-vscode" - ] - } - }, - - "forwardPorts": [3000, 3002, 5432], - - "postCreateCommand": "bash -c 'bash .devcontainer/post-create.sh || true'", - - "remoteUser": "bun" -} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml deleted file mode 100644 index 2689fd7b689..00000000000 --- a/.devcontainer/docker-compose.yml +++ /dev/null @@ -1,99 +0,0 @@ -services: - app: - build: - context: .. - dockerfile: .devcontainer/Dockerfile - volumes: - - ..:/workspace:cached - - bun-cache:/home/bun/.bun/cache:delegated - command: sleep infinity - deploy: - resources: - limits: - memory: 8G - environment: - - NODE_ENV=development - - DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio - - BETTER_AUTH_URL=http://localhost:3000 - - NEXT_PUBLIC_APP_URL=http://localhost:3000 - - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here} - - ENCRYPTION_KEY=${ENCRYPTION_KEY:-your_encryption_key_here} - - COPILOT_API_KEY=${COPILOT_API_KEY} - - MSHIP_SYSPROMPT_OVERRIDE=${MSHIP_SYSPROMPT_OVERRIDE:-} - - NEXT_PUBLIC_CHAT_DISABLED=${NEXT_PUBLIC_CHAT_DISABLED:-} - - SIM_AGENT_API_URL=${SIM_AGENT_API_URL} - - OLLAMA_URL=${OLLAMA_URL:-http://localhost:11434} - - NEXT_PUBLIC_SOCKET_URL=${NEXT_PUBLIC_SOCKET_URL:-} - - BUN_INSTALL_CACHE_DIR=/home/bun/.bun/cache - # Lets a workflow reach a service on the Docker host. Reaching it also - # requires naming it in EGRESS_ALLOWED_HOSTS; this only makes the name - # resolve, which it does not on Linux by default. - extra_hosts: - - 'host.docker.internal:host-gateway' - depends_on: - db: - condition: service_healthy - migrations: - condition: service_completed_successfully - ports: - - "3000:3000" - - "3001:3001" - working_dir: /workspace - - realtime: - build: - context: .. - dockerfile: .devcontainer/Dockerfile - volumes: - - ..:/workspace:cached - - bun-cache:/home/bun/.bun/cache:delegated - command: sleep infinity - deploy: - resources: - limits: - memory: 1G - environment: - - NODE_ENV=development - - DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio - - BETTER_AUTH_URL=http://localhost:3000 - - NEXT_PUBLIC_APP_URL=http://localhost:3000 - - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here} - depends_on: - db: - condition: service_healthy - ports: - - "3002:3002" - working_dir: /workspace - - migrations: - build: - context: .. - dockerfile: docker/db.Dockerfile - environment: - - DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio - depends_on: - db: - condition: service_healthy - command: ['bun', 'run', 'db:migrate'] - restart: 'no' - - db: - image: pgvector/pgvector:pg17 - restart: unless-stopped - volumes: - - postgres-data:/var/lib/postgresql/data - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - POSTGRES_DB=simstudio - ports: - - "${POSTGRES_PORT:-5432}:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 5s - timeout: 5s - retries: 5 - -volumes: - postgres-data: - bun-cache: diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh deleted file mode 100755 index 841d5c2e362..00000000000 --- a/.devcontainer/post-create.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/bash - -# Exit on error, but with some error handling -set -e - -echo "πŸ”§ Setting up Sim development environment..." - -# Change to the workspace root directory -cd /workspace - -# Install global packages for development (done at runtime, not build time) -echo "πŸ“¦ Installing global development tools..." -bun install -g turbo drizzle-kit typescript @types/node 2>/dev/null || { - echo "⚠️ Some global packages may already be installed, continuing..." -} - -# Set up bun completions (with proper shell detection) -echo "πŸ”§ Setting up shell completions..." -if [ -n "$SHELL" ] && [ -f "$SHELL" ]; then - SHELL=/bin/bash bun completions 2>/dev/null | sudo tee /etc/bash_completion.d/bun > /dev/null || { - echo "⚠️ Could not install bun completions, but continuing..." - } -fi - -# Add project commands to shell profile -echo "πŸ“„ Setting up project commands..." -# Add sourcing of sim-commands.sh to user's shell config files if they exist -for rcfile in ~/.bashrc ~/.zshrc; do - if [ -f "$rcfile" ]; then - # Check if already added - if ! grep -q "sim-commands.sh" "$rcfile"; then - echo "" >> "$rcfile" - echo "# Sim project commands" >> "$rcfile" - echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> "$rcfile" - echo " source /workspace/.devcontainer/sim-commands.sh" >> "$rcfile" - echo "fi" >> "$rcfile" - fi - fi -done - -# If no rc files exist yet, create a minimal one -if [ ! -f ~/.bashrc ] && [ ! -f ~/.zshrc ]; then - echo "# Source Sim project commands" > ~/.bashrc - echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> ~/.bashrc - echo " source /workspace/.devcontainer/sim-commands.sh" >> ~/.bashrc - echo "fi" >> ~/.bashrc -fi - -# Clean and reinstall dependencies to ensure platform compatibility -echo "πŸ“¦ Cleaning and reinstalling dependencies..." -if [ -d "node_modules" ]; then - echo "Removing existing node_modules to ensure platform compatibility..." - rm -rf node_modules - rm -rf apps/sim/node_modules - rm -rf apps/docs/node_modules -fi - -# Ensure Bun cache directory exists and has correct permissions -mkdir -p ~/.bun/cache -chmod 700 ~/.bun ~/.bun/cache - -# Install dependencies with platform-specific binaries -echo "Installing dependencies with Bun..." -bun install - -# Check for native dependencies -echo "Checking for native dependencies compatibility..." -if grep -q '"trustedDependencies"' apps/sim/package.json 2>/dev/null; then - echo "⚠️ Native dependencies detected. Bun will handle compatibility during install." -fi - -# Set up environment variables if .env doesn't exist for the sim app -if [ ! -f "apps/sim/.env" ]; then - echo "πŸ“„ Creating apps/sim/.env from template..." - if [ -f "apps/sim/.env.example" ]; then - cp apps/sim/.env.example apps/sim/.env - else - echo "DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio" > apps/sim/.env - fi -fi - -# Set up env for the realtime server (must match the shared values in apps/sim/.env) -if [ ! -f "apps/realtime/.env" ] && [ -f "apps/realtime/.env.example" ]; then - echo "πŸ“„ Creating apps/realtime/.env from template..." - cp apps/realtime/.env.example apps/realtime/.env -fi - -# Set up packages/db/.env for drizzle-kit and migration scripts -if [ ! -f "packages/db/.env" ] && [ -f "packages/db/.env.example" ]; then - echo "πŸ“„ Creating packages/db/.env from template..." - cp packages/db/.env.example packages/db/.env -fi - -# Generate schema and run database migrations -echo "πŸ—ƒοΈ Running database schema generation and migrations..." -echo "Generating schema..." -cd packages/db -bun run db:generate -cd ../.. - -echo "Waiting for database to be ready..." -# Try to connect to the database, but don't fail the script if it doesn't work -( - timeout=60 - while [ $timeout -gt 0 ]; do - if PGPASSWORD=postgres psql -h db -U postgres -c '\q' 2>/dev/null; then - echo "Database is ready!" - cd packages/db - DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio bun run db:push - cd ../.. - break - fi - echo "Database is unavailable - sleeping (${timeout}s remaining)" - sleep 5 - timeout=$((timeout - 5)) - done - - if [ $timeout -le 0 ]; then - echo "⚠️ Database connection timed out, skipping migrations" - fi -) || echo "⚠️ Database setup had issues but continuing..." - -# Clear the welcome message flag to ensure it shows after setup -unset SIM_WELCOME_SHOWN - -echo "" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "βœ… Sim development environment setup complete!" -echo "" -echo "Your environment is now ready. A new terminal session will show" -echo "available commands. You can start the development server with:" -echo "" -echo " sim-start" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "" - -# Exit successfully regardless of any previous errors -exit 0 \ No newline at end of file diff --git a/.devcontainer/sim-commands.sh b/.devcontainer/sim-commands.sh deleted file mode 100755 index c8e3ab1a52f..00000000000 --- a/.devcontainer/sim-commands.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# Sim Project Commands -# Source this file to add project-specific commands to your shell -# Add to your ~/.bashrc or ~/.zshrc: source /workspace/.devcontainer/sim-commands.sh - -# Project-specific aliases for Sim development -alias sim-start="cd /workspace && bun run dev:full" -alias sim-app="cd /workspace && bun run dev" -alias sim-sockets="cd /workspace && bun run dev:sockets" -alias sim-migrate="cd /workspace/packages/db && bun run db:push" -alias sim-generate="cd /workspace/packages/db && bun run db:generate" -alias sim-rebuild="cd /workspace && bun run build && bun run start" -alias docs-dev="cd /workspace/apps/docs && bun run dev" - -# Database connection helpers -alias pgc="PGPASSWORD=postgres psql -h db -U postgres -d simstudio" -alias check-db="PGPASSWORD=postgres psql -h db -U postgres -c '\l'" - -# Default to workspace directory -cd /workspace 2>/dev/null || true - -# Welcome message - show once per session -if [ -z "$SIM_WELCOME_SHOWN" ]; then - export SIM_WELCOME_SHOWN=1 - - echo "" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "πŸš€ Sim Development Environment" - echo "" - echo "Project commands:" - echo " sim-start - Start app + socket server" - echo " sim-app - Start only main app" - echo " sim-sockets - Start only socket server" - echo " sim-migrate - Push schema changes" - echo " sim-generate - Generate migrations" - echo "" - echo "Database:" - echo " pgc - Connect to PostgreSQL" - echo " check-db - List databases" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo "" -fi diff --git a/.dockerignore b/.dockerignore index 3a5d3e50438..f2a2426339e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -25,7 +25,6 @@ docs/ # CI/CD and DevOps .github -.devcontainer .husky docker-compose*.yml Dockerfile* diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d3637761fe8..1fd8871894b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -208,44 +208,9 @@ To use local models with Sim: docker compose -f docker-compose.prod.yml up -d ``` -### Option 3: Using VS Code / Cursor Dev Containers +### Option 3: Manual Setup -Dev Containers provide a consistent and easy-to-use development environment: - -1. **Prerequisites:** - - - Visual Studio Code or Cursor - - Docker Desktop - - [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension for VS Code - -2. **Setup Steps:** - - - Clone the repository: - - ```bash - git clone https://github.com//sim.git - cd sim - ``` - - - Open the project in VS Code/Cursor. - - When prompted, click "Reopen in Container" (or press F1 and select "Remote-Containers: Reopen in Container"). - - Wait for the container to build and initialize. - -3. **Start Developing:** - - - Run `bun run dev:full` in the terminal or use the `sim-start` alias. - - This starts both the main application and the realtime socket server. - - All dependencies and configurations are automatically set up. - - Your changes will be automatically hot-reloaded. - -4. **GitHub Codespaces:** - - - This setup also works with GitHub Codespaces if you prefer development in the browser. - - Just click "Code" β†’ "Codespaces" β†’ "Create codespace on staging". - -### Option 4: Manual Setup - -If you prefer not to use Docker or Dev Containers. **All commands run from the repository root unless explicitly noted.** +If you prefer not to use Docker. **All commands run from the repository root unless explicitly noted.** 1. **Clone and Install:** diff --git a/.github/workflows/ci-cache-cleanup.yml b/.github/workflows/ci-cache-cleanup.yml deleted file mode 100644 index 2888e7f0aee..00000000000 --- a/.github/workflows/ci-cache-cleanup.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: CI Cache Cleanup - -# DRAINING LEGACY DISKS ONLY. test-build.yml no longer mounts a Next.js build -# cache β€” the Turbopack persistent cache measured 3.2x SLOWER than no cache, so it -# is off. But every PR open while the per-branch key was live left a 5-12 GB volume -# behind, and nothing else reclaims them. This keeps deleting them as those PRs -# close. -# -# Delete this workflow once the backlog is drained (no PR predating the cache -# removal is still open). It is a no-op for new PRs, which never create a disk. - -on: - pull_request: - types: [closed] - -permissions: - contents: read - -jobs: - delete-nextjs-cache: - name: Delete Next.js build cache disk - # Sticky disks only exist on Blacksmith; the GitHub break-glass path uses - # actions/cache, which expires on its own. - if: vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith' - runs-on: blacksmith-2vcpu-ubuntu-2404 - timeout-minutes: 5 - - steps: - # A hard-coded legacy drain key. It no longer mirrors anything β€” the - # Mount Next.js build cache step it used to match was removed with the - # cache. Do not retarget or delete it while PRs from before that removal - # are still open, or their 5-12 GB disks are never reclaimed. - # Non-blocking: PRs skipped by ci.yml's paths-ignore never made a disk, - # and neither does any PR opened after the removal. - - name: Delete sticky disk - uses: useblacksmith/stickydisk-delete@b41313d28b8647d72114c9ba3c96bb04061562b6 # v1 - continue-on-error: true - with: - delete-key: ${{ github.repository }}-nextjs-cache-pull_request${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ github.head_ref }} diff --git a/apps/sim/package.json b/apps/sim/package.json index ec558e036bc..aab01234f88 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -20,7 +20,6 @@ "build": "bun run build:sandbox-bundles && NODE_OPTIONS='--max-old-space-size=8192' next build", "build:sandbox-bundles": "bun run ./lib/execution/sandbox/bundles/build.ts", "start": "next start", - "prepare": "cd ../.. && bun husky", "test": "vitest run", "test:scim:e2e": "bun run scripts/test-scim-e2e.ts", "test:watch": "vitest", diff --git a/package.json b/package.json index 080c1582317..768902f6a06 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "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 vitest.scripts.config.ts" + "test:scripts": "vitest run --config scripts/vitest.config.ts" }, "overrides": { "react": "19.2.4", diff --git a/renovate.json b/renovate.json deleted file mode 100644 index eb451c8ebc8..00000000000 --- a/renovate.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended"], - "timezone": "America/Los_Angeles", - "schedule": ["before 6am on Monday"], - "minimumReleaseAge": "7 days", - "osvVulnerabilityAlerts": true, - "vulnerabilityAlerts": { - "enabled": true, - "addLabels": ["security"], - "schedule": ["at any time"], - "minimumReleaseAge": null - }, - "dependencyDashboard": true, - "labels": ["dependencies"], - "prConcurrentLimit": 5, - "prHourlyLimit": 2, - "rangeStrategy": "replace", - "lockFileMaintenance": { - "enabled": true, - "schedule": ["before 6am on Monday"] - }, - "packageRules": [ - { - "description": "Group compatible non-major npm updates that are not in a coupled family", - "matchManagers": ["bun"], - "matchUpdateTypes": ["minor", "patch"], - "groupName": "npm non-major dependencies" - }, - { - "description": "Keep the generated AWS and Smithy release families aligned", - "matchPackageNames": ["@aws-sdk/**", "@smithy/**"], - "groupName": "AWS SDK" - }, - { - "description": "Keep OpenTelemetry API, SDK, and exporters on compatible release lines", - "matchPackageNames": ["@opentelemetry/**"], - "groupName": "OpenTelemetry" - }, - { - "description": "Keep React, Next.js, and their platform packages aligned", - "matchPackageNames": [ - "react", - "react-dom", - "next", - "@next/**", - "@types/react", - "@types/react-dom" - ], - "groupName": "React and Next.js" - }, - { - "description": "Keep Radix primitives on one tested release family", - "matchPackageNames": ["@radix-ui/**"], - "groupName": "Radix UI" - }, - { - "description": "Keep TipTap extensions and ProseMirror bindings aligned", - "matchPackageNames": ["@tiptap/**"], - "groupName": "TipTap" - }, - { - "description": "Keep React Email packages and CLI aligned", - "matchPackageNames": ["react-email", "@react-email/**"], - "groupName": "React Email" - }, - { - "description": "Keep Trigger.dev runtime, build package, and CLI aligned", - "matchPackageNames": ["@trigger.dev/**", "trigger.dev"], - "groupName": "Trigger.dev" - }, - { - "description": "Group immutable GitHub Actions digest refreshes", - "matchManagers": ["github-actions"], - "matchUpdateTypes": ["digest"], - "groupName": "GitHub Actions digests", - "minimumReleaseAge": null - } - ] -} diff --git a/scripts/check-script-test-coverage.ts b/scripts/check-script-test-coverage.ts index 87dc148c458..e92dafdfe13 100644 --- a/scripts/check-script-test-coverage.ts +++ b/scripts/check-script-test-coverage.ts @@ -1,11 +1,11 @@ #!/usr/bin/env bun /** - * Asserts every `scripts/*.test.ts` file is collected by the root Vitest config. + * Asserts every `scripts/*.test.ts` file 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 * entry never runs, in CI or locally, and nothing reports it. `scripts/check-migrations-safety.test.ts` - * sat unreferenced and green for exactly that reason. The root `vitest.scripts.config.ts` now collects + * sat unreferenced and green for exactly that reason. `scripts/vitest.config.ts` now collects * the directory by glob, so drift can only come from a file the glob does not match (a test in a * subdirectory, a different suffix) or from the `test` script no longer chaining `test:scripts`. * This guard checks both by asking Vitest which files it would run. @@ -42,7 +42,7 @@ if (!reachableScripts('test').has('test:scripts')) { } const listed = Bun.spawnSync( - ['bunx', 'vitest', 'list', '--json', '--filesOnly', '--config', 'vitest.scripts.config.ts'], + ['bunx', 'vitest', 'list', '--json', '--filesOnly', '--config', 'scripts/vitest.config.ts'], { cwd: ROOT, } @@ -66,11 +66,11 @@ const orphaned = onDisk.filter((file) => !collected.has(file)) if (orphaned.length > 0) { console.error( `Script tests never run by \`bun run test\`:\n${orphaned.map((file) => ` - ${file}`).join('\n')}\n` + - 'Make sure the root `vitest.scripts.config.ts` include glob matches them.' + 'Make sure the `scripts/vitest.config.ts` include glob matches them.' ) process.exit(1) } console.log( - `Script test coverage passed: ${onDisk.length} script tests collected by the root Vitest config.` + `Script test coverage passed: ${onDisk.length} script tests collected by the scripts Vitest config.` ) diff --git a/scripts/vitest.config.ts b/scripts/vitest.config.ts new file mode 100644 index 00000000000..b80735697ea --- /dev/null +++ b/scripts/vitest.config.ts @@ -0,0 +1,21 @@ +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vitest/config' + +/** + * Repo-level scripts have their own suites. One invocation for all of them + * replaces eighteen sequential `vitest run ` processes, each of which + * paid its own startup. `scripts/openapi` keeps its own config and runs under + * `check:openapi`. + * + * Lives here rather than as a root `vitest.config.ts`: Vitest walks up from a + * package's directory looking for that name, so a root config would silently + * replace the defaults of every workspace package that has none of its own. + * The root is pinned so `bun run test:scripts` behaves the same from any cwd. + */ +export default defineConfig({ + test: { + root: fileURLToPath(new URL('..', import.meta.url)), + environment: 'node', + include: ['scripts/*.test.ts'], + }, +}) diff --git a/vitest.scripts.config.ts b/vitest.scripts.config.ts deleted file mode 100644 index 80d14604d7b..00000000000 --- a/vitest.scripts.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { defineConfig } from 'vitest/config' - -/** - * Repo-level scripts have their own suites. One invocation for all of them - * replaces eleven sequential `vitest run ` processes, each of which paid - * its own startup. `scripts/openapi` keeps its own config and runs under - * `check:openapi`. - * - * Deliberately not named `vitest.config.ts`: Vitest walks up from a package's - * directory looking for that name, so a root config would silently replace - * the defaults of every workspace package that has none of its own. - */ -export default defineConfig({ - test: { - environment: 'node', - include: ['scripts/*.test.ts'], - }, -}) From 4f21be1287f280391f08a2b171bd1a86150ac4ef Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 10 Sep 2026 19:33:51 -0700 Subject: [PATCH 2/4] chore(agents): generate Cursor rules from .claude/rules and symlink AGENTS.md - .claude/rules/.md is now the single source for editor rules; the sync script projects .cursor/rules/.mdc from it (description + globs, or alwaysApply when a rule has no paths). The Cursor copies had drifted since July and five newer rules never reached Cursor - Move the Cursor-only testing rule to .claude/rules/sim-testing.md and add a description to every canonical rule - Pre-commit regenerates on staged rule edits; check:skills verifies - AGENTS.md becomes a symlink to CLAUDE.md; it was a stale subset --- .claude/rules/constitution.md | 4 + .claude/rules/emcn-components.md | 1 + .claude/rules/global.md | 4 + .claude/rules/landing-seo-geo.md | 1 + .claude/rules/sim-architecture.md | 1 + .claude/rules/sim-caching.md | 1 + .claude/rules/sim-components.md | 1 + .claude/rules/sim-hooks.md | 1 + .claude/rules/sim-imports.md | 1 + .claude/rules/sim-integrations.md | 1 + .claude/rules/sim-list-ordering.md | 1 + .claude/rules/sim-queries.md | 1 + .claude/rules/sim-react-performance.md | 4 + .claude/rules/sim-settings-pages.md | 1 + .claude/rules/sim-stores.md | 1 + .claude/rules/sim-styling.md | 1 + .claude/rules/sim-testing.md | 258 +++++++++++++ .claude/rules/sim-typescript.md | 1 + .claude/rules/sim-ui-copy.md | 1 + .claude/rules/sim-url-state.md | 1 + .cursor/rules/constitution.mdc | 12 +- .cursor/rules/emcn-components.mdc | 27 +- .cursor/rules/global.mdc | 13 + .cursor/rules/landing-seo-geo.mdc | 26 +- .cursor/rules/sim-architecture.mdc | 32 ++ .cursor/rules/sim-caching.mdc | 98 +++++ .cursor/rules/sim-components.mdc | 22 ++ .cursor/rules/sim-hooks.mdc | 29 +- .cursor/rules/sim-imports.mdc | 18 +- .cursor/rules/sim-integrations.mdc | 9 +- .cursor/rules/sim-list-ordering.mdc | 151 ++++++++ .cursor/rules/sim-queries.mdc | 44 ++- .cursor/rules/sim-react-performance.mdc | 130 +++++++ .cursor/rules/sim-sandbox.mdc | 13 +- .cursor/rules/sim-settings-pages.mdc | 400 +++++++++++++++++++ .cursor/rules/sim-stores.mdc | 6 +- .cursor/rules/sim-styling.mdc | 35 +- .cursor/rules/sim-testing.mdc | 3 +- .cursor/rules/sim-typescript.mdc | 4 +- .cursor/rules/sim-ui-copy.mdc | 9 +- .cursor/rules/sim-url-state.mdc | 283 ++++++++++++++ .github/CONTRIBUTING.md | 2 + .husky/pre-commit | 10 +- AGENTS.md | 488 +----------------------- CLAUDE.md | 2 +- scripts/sync-skills.ts | 140 ++++++- 46 files changed, 1755 insertions(+), 537 deletions(-) create mode 100644 .claude/rules/sim-testing.md create mode 100644 .cursor/rules/sim-caching.mdc create mode 100644 .cursor/rules/sim-list-ordering.mdc create mode 100644 .cursor/rules/sim-react-performance.mdc create mode 100644 .cursor/rules/sim-settings-pages.mdc create mode 100644 .cursor/rules/sim-url-state.mdc mode change 100644 => 120000 AGENTS.md diff --git a/.claude/rules/constitution.md b/.claude/rules/constitution.md index 62dd7df3c56..565dee4b877 100644 --- a/.claude/rules/constitution.md +++ b/.claude/rules/constitution.md @@ -1,3 +1,7 @@ +--- +description: Sim product language, positioning, and tone guidelines +--- + # Sim β€” Language & Positioning When editing user-facing copy (landing pages, docs, metadata, marketing), follow these rules. diff --git a/.claude/rules/emcn-components.md b/.claude/rules/emcn-components.md index cab88b47f67..f02ff3946e5 100644 --- a/.claude/rules/emcn-components.md +++ b/.claude/rules/emcn-components.md @@ -1,4 +1,5 @@ --- +description: EMCN component library patterns paths: - "packages/emcn/**" --- diff --git a/.claude/rules/global.md b/.claude/rules/global.md index 8ae6e0e874c..90852e76206 100644 --- a/.claude/rules/global.md +++ b/.claude/rules/global.md @@ -1,3 +1,7 @@ +--- +description: Global coding standards that apply to all files +--- + # Global Standards ## Logging diff --git a/.claude/rules/landing-seo-geo.md b/.claude/rules/landing-seo-geo.md index 4f6ec536ad0..51c5402262a 100644 --- a/.claude/rules/landing-seo-geo.md +++ b/.claude/rules/landing-seo-geo.md @@ -1,4 +1,5 @@ --- +description: SEO and GEO guidelines for the landing page paths: - "apps/sim/app/(landing)/**/*.tsx" - "apps/sim/content/**/*.mdx" diff --git a/.claude/rules/sim-architecture.md b/.claude/rules/sim-architecture.md index d950851a3f1..95a59198851 100644 --- a/.claude/rules/sim-architecture.md +++ b/.claude/rules/sim-architecture.md @@ -1,4 +1,5 @@ --- +description: Core architecture principles for the Sim app paths: - "apps/sim/**" --- diff --git a/.claude/rules/sim-caching.md b/.claude/rules/sim-caching.md index fced410b448..45ada29b516 100644 --- a/.claude/rules/sim-caching.md +++ b/.claude/rules/sim-caching.md @@ -1,4 +1,5 @@ --- +description: In-process caching rules: lru-cache with a ceiling, never hand-rolled TTL maps paths: - "apps/sim/lib/**/*.ts" - "apps/sim/providers/**/*.ts" diff --git a/.claude/rules/sim-components.md b/.claude/rules/sim-components.md index 183fbaac822..f435b20c1a8 100644 --- a/.claude/rules/sim-components.md +++ b/.claude/rules/sim-components.md @@ -1,4 +1,5 @@ --- +description: Component patterns and structure for React components paths: - "apps/sim/**/*.tsx" --- diff --git a/.claude/rules/sim-hooks.md b/.claude/rules/sim-hooks.md index 36b96827c82..a4ff58fdbca 100644 --- a/.claude/rules/sim-hooks.md +++ b/.claude/rules/sim-hooks.md @@ -1,4 +1,5 @@ --- +description: Custom hook patterns and best practices paths: - "apps/sim/**/use-*.ts" - "apps/sim/**/hooks/**/*.ts" diff --git a/.claude/rules/sim-imports.md b/.claude/rules/sim-imports.md index 0449d96e11e..3aeafa0dd2e 100644 --- a/.claude/rules/sim-imports.md +++ b/.claude/rules/sim-imports.md @@ -1,4 +1,5 @@ --- +description: Import patterns for the Sim application paths: - "apps/sim/**/*.ts" - "apps/sim/**/*.tsx" diff --git a/.claude/rules/sim-integrations.md b/.claude/rules/sim-integrations.md index 34231a900b9..c4729747596 100644 --- a/.claude/rules/sim-integrations.md +++ b/.claude/rules/sim-integrations.md @@ -1,4 +1,5 @@ --- +description: Adding new integrations (tools, blocks, triggers) paths: - "apps/sim/tools/**" - "apps/sim/blocks/**" diff --git a/.claude/rules/sim-list-ordering.md b/.claude/rules/sim-list-ordering.md index cad6c2104b3..dd183336e3e 100644 --- a/.claude/rules/sim-list-ordering.md +++ b/.claude/rules/sim-list-ordering.md @@ -1,4 +1,5 @@ --- +description: List and menu ordering: mirror the sidebar or toolbar, one separator before the destructive action paths: - "apps/sim/app/**/*.tsx" - "apps/sim/ee/**/*.tsx" diff --git a/.claude/rules/sim-queries.md b/.claude/rules/sim-queries.md index a71ee448dde..f2600e4e264 100644 --- a/.claude/rules/sim-queries.md +++ b/.claude/rules/sim-queries.md @@ -1,4 +1,5 @@ --- +description: React Query patterns for the Sim application paths: - "apps/sim/hooks/queries/**/*.ts" --- diff --git a/.claude/rules/sim-react-performance.md b/.claude/rules/sim-react-performance.md index a78c2e984e4..a9bfeaa7f63 100644 --- a/.claude/rules/sim-react-performance.md +++ b/.claude/rules/sim-react-performance.md @@ -1,3 +1,7 @@ +--- +description: Behavior-preserving React render-performance idioms +--- + # React & Render Performance Behavior-preserving performance idioms for components, hooks, and hot render paths. These are safe defaults β€” apply them freely. For the render-causing *effect/state* anti-patterns (derived state in effects, effect chains, state synced to a prop), use the dedicated skills: `/you-might-not-need-an-effect`, `/you-might-not-need-state`, `/you-might-not-need-a-memo`, `/you-might-not-need-a-callback`. Those refactors change render timing β€” verify them against the running UI, never mass-apply blind. diff --git a/.claude/rules/sim-settings-pages.md b/.claude/rules/sim-settings-pages.md index 9893028c352..aaf692544a1 100644 --- a/.claude/rules/sim-settings-pages.md +++ b/.claude/rules/sim-settings-pages.md @@ -1,4 +1,5 @@ --- +description: Settings page layout and SettingsPanel design-system compliance paths: - "apps/sim/app/workspace/*/settings/**" - "apps/sim/app/workspace/*/{integrations,skills,upgrade}/**" diff --git a/.claude/rules/sim-stores.md b/.claude/rules/sim-stores.md index 273c394fbce..d44078e84bc 100644 --- a/.claude/rules/sim-stores.md +++ b/.claude/rules/sim-stores.md @@ -1,4 +1,5 @@ --- +description: Zustand store patterns paths: - "apps/sim/**/store.ts" - "apps/sim/**/stores/**/*.ts" diff --git a/.claude/rules/sim-styling.md b/.claude/rules/sim-styling.md index 61960c9eb5c..48d9086a481 100644 --- a/.claude/rules/sim-styling.md +++ b/.claude/rules/sim-styling.md @@ -1,4 +1,5 @@ --- +description: Tailwind CSS and styling conventions paths: - "apps/sim/**/*.tsx" - "apps/sim/**/*.css" diff --git a/.claude/rules/sim-testing.md b/.claude/rules/sim-testing.md new file mode 100644 index 00000000000..13c9044e594 --- /dev/null +++ b/.claude/rules/sim-testing.md @@ -0,0 +1,258 @@ +--- +description: Testing patterns with Vitest and @sim/testing +paths: + - "apps/sim/**/*.test.ts" + - "apps/sim/**/*.test.tsx" +--- + +# Testing Patterns + +Use Vitest. Test files: `feature.ts` β†’ `feature.test.ts` + +## Global Mocks (vitest.setup.ts) + +These modules are mocked globally β€” do NOT re-mock them in test files unless you need to override behavior: + +- `@sim/db` β†’ `databaseMock` +- `@sim/db/schema` β†’ `schemaMock` +- `drizzle-orm` β†’ `drizzleOrmMock` +- `@sim/logger` β†’ `loggerMock` +- `@/lib/auth` β†’ `authMock` +- `@/lib/auth/hybrid` β†’ `hybridAuthMock` (with default session-delegating behavior) +- `@/lib/core/utils/request` β†’ `requestUtilsMock` +- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store` +- `@/blocks/registry` +- `@trigger.dev/sdk` +- `@sim/platform-authz/workflow` β†’ `workflowAuthzMock` + +## Structure + +```typescript +/** + * @vitest-environment node + */ +import { createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockGetSession } = vi.hoisted(() => ({ + mockGetSession: vi.fn(), +})) + +vi.mock('@/lib/auth', () => ({ + auth: { api: { getSession: vi.fn() } }, + getSession: mockGetSession, +})) + +import { GET, POST } from '@/app/api/my-route/route' + +describe('my route', () => { + beforeEach(() => { + vi.clearAllMocks() + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + }) + + it('returns data', async () => { + const req = createMockRequest('GET') + const res = await GET(req) + expect(res.status).toBe(200) + }) +}) +``` + +## Performance Rules (Critical) + +### NEVER use `vi.resetModules()` + `vi.doMock()` + `await import()` + +This is the #1 cause of slow tests. It forces complete module re-evaluation per test. + +```typescript +// BAD β€” forces module re-evaluation every test (~50-100ms each) +beforeEach(() => { + vi.resetModules() + vi.doMock('@/lib/auth', () => ({ getSession: vi.fn() })) +}) +it('test', async () => { + const { GET } = await import('./route') // slow dynamic import +}) + +// GOOD β€” module loaded once, mocks reconfigured per test (~1ms each) +const { mockGetSession } = vi.hoisted(() => ({ + mockGetSession: vi.fn(), +})) +vi.mock('@/lib/auth', () => ({ getSession: mockGetSession })) +import { GET } from '@/app/api/my-route/route' + +beforeEach(() => { vi.clearAllMocks() }) +it('test', () => { + mockGetSession.mockResolvedValue({ user: { id: '1' } }) +}) +``` + +**Only exception:** Singleton modules that cache state at module scope (e.g., Redis clients, connection pools). These genuinely need `vi.resetModules()` + dynamic import to get a fresh instance per test. + +### NEVER use `vi.importActual()` + +This defeats the purpose of mocking by loading the real module and all its dependencies. + +```typescript +// BAD β€” loads real module + all transitive deps +vi.mock('@/lib/workspaces/utils', async () => { + const actual = await vi.importActual('@/lib/workspaces/utils') + return { ...actual, myFn: vi.fn() } +}) + +// GOOD β€” mock everything, only implement what tests need +vi.mock('@/lib/workspaces/utils', () => ({ + myFn: vi.fn(), + otherFn: vi.fn(), +})) +``` + +### Mock heavy transitive dependencies + +If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them: + +```typescript +vi.mock('@/blocks', () => ({ + getBlock: () => null, + getAllBlocks: () => ({}), + getAllBlockTypes: () => [], + registry: {}, +})) +``` + +### Use `@vitest-environment node` unless DOM is needed + +Only use `@vitest-environment jsdom` if the test uses `window`, `document`, `FormData`, or other browser APIs. Node environment is significantly faster. + +### Avoid real timers in tests + +```typescript +// BAD +await new Promise(r => setTimeout(r, 500)) + +// GOOD β€” use minimal delays or fake timers +await new Promise(r => setTimeout(r, 1)) +// or +vi.useFakeTimers() +``` + +## Centralized Mocks (prefer over local declarations) + +`@sim/testing` exports ready-to-use mock modules for common dependencies. Import and pass directly to `vi.mock()` β€” no `vi.hoisted()` boilerplate needed. Each paired `*MockFns` object exposes the underlying `vi.fn()`s for per-test overrides. + +| Module mocked | Import | Factory form | +|---|---|---| +| `@/app/api/auth/oauth/utils` | `authOAuthUtilsMock`, `authOAuthUtilsMockFns` | `vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock)` | +| `@/app/api/knowledge/utils` | `knowledgeApiUtilsMock`, `knowledgeApiUtilsMockFns` | `vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock)` | +| `@/app/api/workflows/utils` | `workflowsApiUtilsMock`, `workflowsApiUtilsMockFns` | `vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock)` | +| `@sim/audit` | `auditMock`, `auditMockFns` | `vi.mock('@sim/audit', () => auditMock)` | +| `@/lib/auth` | `authMock`, `authMockFns` | `vi.mock('@/lib/auth', () => authMock)` | +| `@/lib/auth/hybrid` | `hybridAuthMock`, `hybridAuthMockFns` | `vi.mock('@/lib/auth/hybrid', () => hybridAuthMock)` | +| `@/lib/copilot/request/http` | `copilotHttpMock`, `copilotHttpMockFns` | `vi.mock('@/lib/copilot/request/http', () => copilotHttpMock)` | +| `@/lib/core/config/env` | `envMock`, `createEnvMock(overrides)` | `vi.mock('@/lib/core/config/env', () => envMock)` | +| `@/lib/core/config/env-flags` | `featureFlagsMock` | `vi.mock('@/lib/core/config/env-flags', () => featureFlagsMock)` | +| `@/lib/core/config/redis` | `redisConfigMock`, `redisConfigMockFns` | `vi.mock('@/lib/core/config/redis', () => redisConfigMock)` | +| `@/lib/core/security/encryption` | `encryptionMock`, `encryptionMockFns` | `vi.mock('@/lib/core/security/encryption', () => encryptionMock)` | +| `@/lib/core/security/input-validation.server` | `inputValidationMock`, `inputValidationMockFns` | `vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock)` | +| `@/lib/core/utils/request` | `requestUtilsMock`, `requestUtilsMockFns` | `vi.mock('@/lib/core/utils/request', () => requestUtilsMock)` | +| `@/lib/core/utils/urls` | `urlsMock`, `urlsMockFns` | `vi.mock('@/lib/core/utils/urls', () => urlsMock)` | +| `@/lib/execution/preprocessing` | `executionPreprocessingMock`, `executionPreprocessingMockFns` | `vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock)` | +| `@/lib/logs/execution/logging-session` | `loggingSessionMock`, `loggingSessionMockFns`, `LoggingSessionMock` | `vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock)` | +| `@/lib/workflows/orchestration` | `workflowsOrchestrationMock`, `workflowsOrchestrationMockFns` | `vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock)` | +| `@/lib/workflows/persistence/utils` | `workflowsPersistenceUtilsMock`, `workflowsPersistenceUtilsMockFns` | `vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock)` | +| `@/lib/workflows/utils` | `workflowsUtilsMock`, `workflowsUtilsMockFns` | `vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock)` | +| `@/lib/workspaces/permissions/utils` | `permissionsMock`, `permissionsMockFns` | `vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock)` | +| `@sim/db/schema` | `schemaMock` | `vi.mock('@sim/db/schema', () => schemaMock)` | + +### Auth mocking (API routes) + +```typescript +import { authMock, authMockFns } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +vi.mock('@/lib/auth', () => authMock) + +import { GET } from '@/app/api/my-route/route' + +beforeEach(() => { + vi.clearAllMocks() + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) +}) +``` + +Only define a local `vi.mock('@/lib/auth', ...)` if the module under test consumes exports outside the centralized shape (e.g., `auth.api.verifyOneTimeToken`, `auth.api.resetPassword`). + +### Hybrid auth mocking + +```typescript +import { hybridAuthMock, hybridAuthMockFns } from '@sim/testing' + +vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) + +// In tests: +hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, userId: 'user-1', authType: 'session', +}) +``` + +### Database chain mocking + +Use the centralized `dbChainMock` + `dbChainMockFns` helpers β€” no `vi.hoisted()` or chain-wiring boilerplate needed. + +```typescript +import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' + +vi.mock('@sim/db', () => dbChainMock) +// Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} })) + +beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() // only needed if tests use permanent (non-`Once`) overrides +}) + +it('reads a row', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([{ id: '1', name: 'test' }]) + // exercise code that hits db.select().from().where().limit() + expect(dbChainMockFns.where).toHaveBeenCalled() +}) +``` + +**Default chains supported:** +- `select()/selectDistinct()/selectDistinctOn() β†’ from() β†’ where()/innerJoin()/leftJoin() β†’ where() β†’ limit()/orderBy()/returning()/groupBy()/for()` +- `insert() β†’ values() β†’ returning()/onConflictDoUpdate()/onConflictDoNothing()` +- `update() β†’ set() β†’ where() β†’ limit()/orderBy()/returning()/for()` +- `delete() β†’ where() β†’ limit()/orderBy()/returning()/for()` +- `db.execute()` resolves `[]` +- `db.transaction(cb)` calls cb with `dbChainMock.db` + +`.for('update')` (Postgres row-level locking) is supported on `where` builders. It returns a thenable with `.limit` / `.orderBy` / `.returning` / `.groupBy` attached, so both `await .where().for('update')` (terminal) and `await .where().for('update').limit(1)` (chained) work. Override the terminal result with `dbChainMockFns.for.mockResolvedValueOnce([...])`; for the chained form, mock the downstream terminal (e.g. `dbChainMockFns.limit.mockResolvedValueOnce([...])`). + +All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns..mockResolvedValueOnce(...)`. Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent); tests using only `...Once` variants don't need it. + +## @sim/testing Package + +Always prefer over local test data. + +| Category | Utilities | +|----------|-----------| +| **Module mocks** | See "Centralized Mocks" table above | +| **Logger helpers** | `loggerMock`, `createMockLogger()`, `getLoggerCalls()`, `clearLoggerMocks()` | +| **Database helpers** | `databaseMock`, `drizzleOrmMock`, `createMockDb()`, `createMockSql()`, `createMockSqlOperators()` | +| **Fetch helpers** | `setupGlobalFetchMock()`, `createMockFetch()`, `createMockResponse()`, `mockFetchError()` | +| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` | +| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` | +| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` | +| **Requests** | `createMockRequest()`, `createMockFormDataRequest()` | + +## Rules Summary + +1. `@vitest-environment node` unless DOM is required +2. Prefer centralized mocks from `@sim/testing` (see table above) over local `vi.hoisted()` + `vi.mock()` boilerplate +3. `vi.hoisted()` + `vi.mock()` + static imports β€” never `vi.resetModules()` + `vi.doMock()` + dynamic imports +4. `vi.mock()` calls before importing mocked modules +5. `beforeEach(() => vi.clearAllMocks())` to reset state β€” no redundant `afterEach` +6. No `vi.importActual()` β€” mock everything explicitly +7. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them +8. Use absolute imports in test files +9. Avoid real timers β€” use 1ms delays or `vi.useFakeTimers()` diff --git a/.claude/rules/sim-typescript.md b/.claude/rules/sim-typescript.md index 526eefd6499..e381c7d1bbf 100644 --- a/.claude/rules/sim-typescript.md +++ b/.claude/rules/sim-typescript.md @@ -1,4 +1,5 @@ --- +description: TypeScript conventions and type safety paths: - "apps/sim/**/*.ts" - "apps/sim/**/*.tsx" diff --git a/.claude/rules/sim-ui-copy.md b/.claude/rules/sim-ui-copy.md index 951e4a15367..b676483f41f 100644 --- a/.claude/rules/sim-ui-copy.md +++ b/.claude/rules/sim-ui-copy.md @@ -1,4 +1,5 @@ --- +description: UI copy conventions β€” no default subtitles or helper text under headings, labels, cards, or settings paths: - "apps/sim/**/*.tsx" - "apps/sim/components/emcn/**" diff --git a/.claude/rules/sim-url-state.md b/.claude/rules/sim-url-state.md index 6790bc1b56d..69d8c3aec92 100644 --- a/.claude/rules/sim-url-state.md +++ b/.claude/rules/sim-url-state.md @@ -1,4 +1,5 @@ --- +description: Shareable client view-state lives in the URL via nuqs paths: - "apps/sim/app/**/*.tsx" - "apps/sim/app/**/*.ts" diff --git a/.cursor/rules/constitution.mdc b/.cursor/rules/constitution.mdc index dbce52e5298..468fbd924df 100644 --- a/.cursor/rules/constitution.mdc +++ b/.cursor/rules/constitution.mdc @@ -1,8 +1,10 @@ --- description: Sim product language, positioning, and tone guidelines -globs: ["apps/sim/app/(landing)/**", "apps/docs/**", "apps/sim/app/manifest.ts", "apps/sim/app/sitemap.ts", "apps/sim/app/robots.ts", "apps/sim/app/llms.txt/**", "apps/sim/app/llms-full.txt/**", "apps/sim/app/(landing)/**/structured-data*", "apps/docs/**/structured-data*", "**/metadata*", "**/seo*"] +alwaysApply: true --- + + # Sim β€” Language & Positioning When editing user-facing copy (landing pages, docs, metadata, marketing), follow these rules. @@ -28,7 +30,8 @@ Sim is the **AI workspace** where teams build and run AI agents. Not a workflow | The product | "AI workspace" | "workflow tool", "automation platform", "agent framework" | | Building | "build agents", "create agents" | "create workflows" (unless describing the workflow module specifically) | | Visual builder | "workflow builder" or "visual builder" | "canvas", "graph editor" | -| Mothership | "Mothership" (capitalized) | "chat", "AI assistant", "copilot" | +| The agent | "Sim" β€” you talk to Sim | "Mothership", "copilot", "AI assistant" | +| The chat surface | "Chat" (capitalized, the module) | "Mothership", "copilot" | | Deployment | "deploy", "ship" | "publish", "activate" | | Audience | "teams", "builders" | "users", "customers" (in marketing copy) | | What agents do | "automate real work" | "automate tasks", "automate workflows" | @@ -55,7 +58,7 @@ When describing Sim, always lead with the most differentiated claim: | Module | One-liner | |--------|-----------| -| **Mothership** | Your AI command center. Build and manage everything in natural language. | +| **Chat** | Your AI command center. Talk to Sim β€” build and manage everything in natural language. | | **Workflows** | The visual builder. Connect blocks, models, and integrations into agent logic. | | **Knowledge Base** | Your agents' memory. Upload docs, sync sources, build vector databases. | | **Tables** | A database, built in. Store, query, and wire structured data into agent runs. | @@ -70,7 +73,8 @@ When describing Sim, always lead with the most differentiated claim: - Never promise unshipped features - Never use jargon ("RAG", "vector database", "MCP") without plain-English explanation on public pages - Avoid "agentic workforce" as a primary term β€” use "AI agents" +- Never say "Mothership" or "copilot" β€” the agent is "Sim", the surface is "Chat" (in run logs the trigger reads "Sim agent") ## Vision -Sim becomes the default environment where teams build AI agents β€” not a tool you visit for one task, but a workspace you live in. Workflows are one module; Mothership is another. The workspace is the constant; the interface adapts. +Sim becomes the default environment where teams build AI agents β€” not a tool you visit for one task, but a workspace you live in. Workflows are one module; Chat is another. The workspace is the constant; the interface adapts. diff --git a/.cursor/rules/emcn-components.mdc b/.cursor/rules/emcn-components.mdc index e9307f6dccf..18bb68a23fd 100644 --- a/.cursor/rules/emcn-components.mdc +++ b/.cursor/rules/emcn-components.mdc @@ -2,15 +2,18 @@ description: EMCN component library patterns globs: ["packages/emcn/**"] --- + + + # EMCN Components -Import from `@sim/emcn`, never from subpaths (except CSS files). The **chip family** is the platform's primary chrome β€” always reach for it over the legacy primitives it is progressively replacing (`Input`β†’`ChipInput`, `Textarea`β†’`ChipTextarea`, `Modal`β†’`ChipModal`, `Select`/`Combobox`β†’`ChipSelect`/`ChipCombobox`/`ChipDropdown`, `Switch`β†’`ChipSwitch`, date fieldβ†’`ChipDatePicker`). For context/action menus the canonical control is `DropdownMenu` β€” the standard menu (not a chip, and never a hand-rolled popover). +Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons come from the `@sim/emcn/icons` subpath, and CSS modules from their file path. Never deep-import other component subpaths. The **chip family** is the platform's primary chrome β€” always reach for it over the legacy primitives it is progressively replacing (`Input`β†’`ChipInput`, `Textarea`β†’`ChipTextarea`, `Modal`β†’`ChipModal`, `Select`/`Combobox`β†’`ChipSelect`/`ChipCombobox`/`ChipDropdown`, `Switch`β†’`ChipSwitch`, date fieldβ†’`ChipDatePicker`). For context/action menus the canonical control is `DropdownMenu` β€” the standard menu (not a chip, and never a hand-rolled popover). ## Chip chrome β€” single source of truth Never hand-roll the chip pill from raw class strings (they go stale). Compose from the canonical sources: -- **Surface, typography + content tokens:** `chip/chip-chrome.ts` β€” `chipFilledSurfaceTokens`, `chipFieldSurfaceClass`, `chipFieldTextClass` (text fields and the dropdown search box build on these), plus the chip-content chrome `chipContentGap`, `chipGeometryClass`, `chipContentIconClass`, `chipContentLabelClass`, and `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells). All are re-exported from the `@sim/emcn` barrel β€” no subpath import needed. +- **Surface, typography + content tokens:** `chip/chip-chrome.ts` β€” `chipFilledSurfaceTokens`, `chipFieldSurfaceClass`, `chipFieldTextClass` (text fields and the dropdown search box build on these), plus the chip-content chrome `chipContentGap`, `chipGeometryClass`, `chipContentIconClass`, `chipContentLabelClass`, `cellIconNodeClass` (non-chip surfaces that must visually match chip content, e.g. resource table cells), and the row-state pair `chipHoverSurfaceClass` / `chipActiveSurfaceClass` (hover vs. selected β€” mutually exclusive, so a selected row holds its surface through hover; every hand-rolled row imports these rather than restating the literals). All are re-exported from the `@sim/emcn` barrel β€” no subpath import needed. - **Pill geometry:** `chip/chip.tsx` β€” `chipVariants` (30px tall, `rounded-lg`, `px-2`, icon↔text `gap-1.5`). Every pill-shaped trigger (`ChipDropdown`, `ChipSelect`, `ChipSwitch`) reuses it for visual parity. Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value text `--text-body`, icons `--text-icon` at `size-[14px]`, placeholder `--text-muted`, `transition-colors`, **no focus ring** (the caret marks focus). Filled surface is `--surface-5` light / `--surface-4` dark with a `--border-1` border. @@ -19,16 +22,30 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items ## Component catalogue -- **`Chip` / `ChipLink`** β€” the pill button (`