diff --git a/.agents/skills/add-settings-page/SKILL.md b/.agents/skills/add-settings-page/SKILL.md index 94ff0f182e7..794d52d3e02 100644 --- a/.agents/skills/add-settings-page/SKILL.md +++ b/.agents/skills/add-settings-page/SKILL.md @@ -1,10 +1,12 @@ --- name: add-settings-page -description: Add a new Sim settings page, or audit existing settings pages for design-system compliance with the shared SettingsPanel layout. Use when creating a settings tab, or when asked to check/clean up settings pages so they match the design system (consistent title, header, search, spacing). +description: Add or audit settings pages that use SettingsPanel in the existing Sim app. Use implement-platform for isolated Platform settings. --- # Settings Page (add / audit) +For `apps/sim/platform/**`, use [implement-platform](../implement-platform/SKILL.md). In a mixed request, apply the steps below only to the existing app's settings. + Settings page chrome (header bar, scroll region, content column, nav-driven title + description) is owned by the `settings/[section]/layout.tsx` shell. Each page renders through **`SettingsPanel`**, which registers the page's header data @@ -45,7 +47,7 @@ For each page component, confirm the checklist in `.claude/rules/sim-settings-pa Each grep lists candidates; review every match against the expected ones named below. 1. Find hand-rolled shells that should be `SettingsPanel`: - `git grep -n "flex h-full flex-col bg-\[var(--bg)\]" -- 'apps/sim/**/settings/**' 'apps/sim/ee/'` + `git grep -n "flex h-full flex-col bg-\[var(--bg)\]" -- 'apps/sim/**/settings/**' 'apps/sim/ee/' ':(exclude)apps/sim/platform/**'` — expected matches: the workspace and organization `settings/layout.tsx` shells and the shared header shell (`components/settings/settings-header.tsx`); an entitlement/loading gate is also fine. `CredentialDetailLayout` (the `settings/secrets/[credentialId]` exception) is @@ -53,12 +55,12 @@ Each grep lists candidates; review every match against the expected ones named b passes `back={{ text, icon: ArrowLeft, onSelect }}` to `SettingsPanel`. Anything else is a violation: render it through `SettingsPanel`. 2. Find hand-rolled title blocks: - `git grep -n "text-\[var(--text-body)\] text-lg" -- 'apps/sim/**/settings/**' 'apps/sim/ee/'` + `git grep -n "text-\[var(--text-body)\] text-lg" -- 'apps/sim/**/settings/**' 'apps/sim/ee/' ':(exclude)apps/sim/platform/**'` — the only title is the `

` in `settings-header.tsx`; a non-heading value at that size (e.g. the credit balance in `ee/organization-usage/components/usage-credits.tsx`) is fine. 3. Find literal pixel text sizes (should be 0 — see "Text Scale" in `.claude/rules/sim-styling.md`): - `git grep -nE "text-\[1[0-8]px\]" -- 'apps/sim/**/settings/**' 'apps/sim/ee/'`. + `git grep -nE "text-\[1[0-8]px\]" -- 'apps/sim/**/settings/**' 'apps/sim/ee/' ':(exclude)apps/sim/platform/**'`. Display type above the scale (`text-[40px]` hero headings) is deliberate and out of scope. 4. Confirm each page imports `SettingsPanel` and that its registry entry has an @@ -66,7 +68,7 @@ Each grep lists candidates; review every match against the expected ones named b - Editable pages: confirm Save/Discard go through `saveDiscardActions()` and dirty is wired via `useSettingsUnsavedGuard` (called before early-return gates) — flag any hand-rolled Save button, `beforeunload`, or unsaved modal. - `git grep -n "beforeunload" -- 'apps/sim/**/settings/**' 'apps/sim/ee/' 'apps/sim/components/settings/' ':(exclude,glob)**/*.test.*'` + `git grep -n "beforeunload" -- 'apps/sim/**/settings/**' 'apps/sim/ee/' 'apps/sim/components/settings/' ':(exclude)apps/sim/platform/**' 'apps/sim/hooks/use-settings-browser-navigation.ts' ':(exclude,glob)**/*.test.*'` should only hit the centralized `use-settings-browser-navigation.ts`. 5. Fix each finding with the smallest structural change that satisfies the checklist; do not touch handlers, state, queries, or gate returns. A pixel-size fix swaps diff --git a/.agents/skills/cleanup/SKILL.md b/.agents/skills/cleanup/SKILL.md index 1ddfddcce6e..af9eb79f1f2 100644 --- a/.agents/skills/cleanup/SKILL.md +++ b/.agents/skills/cleanup/SKILL.md @@ -25,7 +25,7 @@ Run these in parallel on the parsed `scope`: 3. `/you-might-not-need-a-callback fix=false` 4. `/you-might-not-need-state fix=false` 5. `/react-query-best-practices fix=false` -6. `/emcn-design-review fix=false` +6. Design review: route `apps/sim/platform/**` to [implement-platform](../implement-platform/SKILL.md) and existing app EMCN paths to [emcn-design-review](../emcn-design-review/SKILL.md). For a mixed diff, the same pass reviews each subset with its own contract. Both reviews are read-only and return proposals for Step 2. 7. `/you-might-not-need-url-state fix=false` 8. `/you-might-not-need-a-comment fix=false` 9. `/test-audit audit ` — read-only; only when the scope adds or changes test files (`*.test.ts(x)`, `*.integration.ts`, `**/e2e/**`, `apps/sim/scripts/test-*-e2e.ts`). First resolve a free-form scope to the concrete list of added or changed test paths (`git diff --name-only` against the scope's base) and pass those paths. It applies the authoring gate to every new or changed test and proposes deleting the ones that fail it. diff --git a/.agents/skills/emcn-design-review/SKILL.md b/.agents/skills/emcn-design-review/SKILL.md index 8a211f53c6b..8fa95e83a5e 100644 --- a/.agents/skills/emcn-design-review/SKILL.md +++ b/.agents/skills/emcn-design-review/SKILL.md @@ -1,6 +1,6 @@ --- name: emcn-design-review -description: Review UI code for alignment with the emcn design system — components, tokens, patterns, and conventions +description: Review the existing Sim app against packages/emcn. Route isolated Platform UI in apps/sim/platform to implement-platform. argument-hint: "[scope] [fix=true|false]" --- @@ -14,7 +14,7 @@ User arguments: $ARGUMENTS ## Context -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. +The existing Sim app uses `packages/emcn`, its Radix primitives, CVA variants, and CSS tokens. For `apps/sim/platform/**`, use [implement-platform](../implement-platform/SKILL.md). For mixed scopes, review each file under its owning system and preserve the requested `fix` mode for both. ## Steps diff --git a/.agents/skills/implement-platform/SKILL.md b/.agents/skills/implement-platform/SKILL.md new file mode 100644 index 00000000000..ccb6593a2b8 --- /dev/null +++ b/.agents/skills/implement-platform/SKILL.md @@ -0,0 +1,16 @@ +--- +name: implement-platform +description: Build, change, consolidate, remove, or review Sim Platform UI in apps/sim/platform. Use for code-first product routes, EMCN components, Paper handoff and design drift in the isolated Next app. +--- + +# Implement Sim Platform + +Start in the Sim checkout containing the requested product work. Read the [Platform contract](../../../apps/sim/platform/AGENTS.md) and [component ownership map](../../../apps/sim/platform/docs/component-ownership.md). These versioned files carry the current rules into a fresh chat. Use the [Platform README](../../../apps/sim/platform/README.md) for the local routes, fixture states, generators, and verification commands. + +1. **Establish scope on the real route.** Identify the product entry, interaction, phase, and affected consumers. When rebuilding an existing workflow, trace the old navigation target, its access gate, and the actions reachable by that viewer. Use that evidence to decide which controls belong on the new page before consulting the broader API or provider catalog. Phase 1 owns foundations and the library. Phase 2 owns working UI and transient interaction state, with examples supplied through the server fixture flag. Phase 3 connects real data and operations. Reuse the same renderer in the browser and Sim Desktop. Read the installed Next guides for the file structure or framework behavior you are changing. + For organization Settings, inventory the old sidebar in order, its viewer/deployment gates and every action reachable from each section. Map old header buttons to the row or section they act on. Verify the same destination and drilldown on the fixture and authenticated routes; keep real writes outside fixture sessions. +2. **Resolve design intent.** For Paper-backed work, use `$sim-paper-design` to locate the live Shared or Platform master and the product states that use it. Exclude Marcus. Preserve the key and theme roles. Treat artboard sizes as reference measurements. For code-first changes, follow the latest explicit product direction and inspect the rendered route. Identify any source you could not verify. Update the Paper canvas when the request includes Paper changes. +3. **Choose or change the owner.** Start with the behavior. An action, navigation row, value picker, or disclosure has an existing keyboard and pointer contract. Different interactions may share visual roles while retaining different semantics. Use the existing family and named props for a change in size, intent, icon, or content. Put unique anatomy and its CSS beside the route component. Keep page files free of styling. Only EMCN owns the appearance of its controls. Use Base UI there for complex keyboard, floating, and dismissal behavior. +4. **Trace the full change.** On a second independent feature use, promote the original implementation and migrate both callers. For consolidation, compare semantics, map every consumer to its replacement, then remove the obsolete implementation, export, variant styles, and unused registrations. For deletion, also check menus, preferences, fixture data, workbench references, and tests that existed only for the removed feature. Preserve unrelated consumers and historical Paper evidence. A new variant needs a reachable product use. Edit the source or compiler for generated foundations and recipes. +5. **Verify where people use it.** Run the design audit and applicable checks from the README. Render the changed product route with empty, full, stress, and fixture-off data as relevant. Check both themes, widths between artboards, long content, text scaling, keyboard, and pointer states. Compare shared controls in their actual consumers. Check the actual scroll edge and the open popup when space is tight. For shell, panel, or desktop work, exercise the real Electron renderer. For substantial implementations and consolidations, use an independent agent during design or implementation and for a final review of the changed owners and routes. Provide the working localhost route for visual review. +6. **Keep decisions durable.** Follow the contract’s maintenance guidance when an approved correction changes a shared rule. Record feature-specific decisions with their feature. For sustained work, keep a task checkpoint with the code revision, verified states, evidence, and outstanding work. Include the branch or PR, checkpoint path, and localhost route in a handoff. On resumption, compare the checkpoint with the current branch, working tree, and relevant live sources before acting. diff --git a/.agents/skills/implement-platform/agents/openai.yaml b/.agents/skills/implement-platform/agents/openai.yaml new file mode 100644 index 00000000000..7f4a26951bd --- /dev/null +++ b/.agents/skills/implement-platform/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Implement Sim Platform" + short_description: "Build and review the code-first Sim Platform" + default_prompt: "Use $implement-platform to implement the next Platform product change in Sim." diff --git a/.claude/rules/emcn-components.md b/.claude/rules/emcn-components.md index 6ed0a0e1d20..28a6db5abd5 100644 --- a/.claude/rules/emcn-components.md +++ b/.claude/rules/emcn-components.md @@ -6,6 +6,8 @@ paths: # EMCN Components +This guide applies to the existing app. For `apps/sim/platform/**`, use `apps/sim/platform/AGENTS.md`. + 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`, date field→`ChipDatePicker`). `ChipSwitch` is a segmented choice between options, not a replacement for `Switch`: a boolean on/off toggle stays `Switch`. 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 diff --git a/.claude/rules/platform-design.md b/.claude/rules/platform-design.md new file mode 100644 index 00000000000..32f7a2dd866 --- /dev/null +++ b/.claude/rules/platform-design.md @@ -0,0 +1,7 @@ +--- +description: Sim Platform implementation from Paper +paths: + - "apps/sim/platform/**" +--- + +Use the canonical contract in `apps/sim/platform/AGENTS.md` and the `implement-platform` skill. Legacy EMCN and app styling rules do not govern this separate project. diff --git a/.claude/rules/sim-architecture.md b/.claude/rules/sim-architecture.md index fd1bda06f19..4cc7f8b423c 100644 --- a/.claude/rules/sim-architecture.md +++ b/.claude/rules/sim-architecture.md @@ -6,6 +6,8 @@ paths: # Sim App Architecture +This guide applies to the existing app. For `apps/sim/platform/**`, use `apps/sim/platform/AGENTS.md`. + Repository layout, package boundaries, the application operation boundary, naming, and utils placement are in the root `CLAUDE.md`. This file holds the app-internal boundaries. ## The `'use client'` server boundary diff --git a/.claude/rules/sim-settings-pages.md b/.claude/rules/sim-settings-pages.md index c39babca114..3dc438d98fb 100644 --- a/.claude/rules/sim-settings-pages.md +++ b/.claude/rules/sim-settings-pages.md @@ -344,7 +344,7 @@ rendered through `` from is what keeps tone/icon/variant/tooltip handling from drifting between the two shells. Reach for it before hand-rolling a `Chip`. - **`useSettingsUnsavedGuard({ isDirty, navigationBlocked, onDiscard })`** from - `@/components/settings/use-settings-unsaved-guard` registers one editor with the + `@/hooks/use-settings-unsaved-guard` registers one editor with the shared settings store. Register every full-page draft, including inline creation forms. Dirty computation stays local and derived; fetched defaults stay clean. - **`onDiscard` resets the registered draft.** The root `SettingsNavigationGuard` diff --git a/.claude/rules/sim-styling.md b/.claude/rules/sim-styling.md index 300c63f9260..3712489f2e7 100644 --- a/.claude/rules/sim-styling.md +++ b/.claude/rules/sim-styling.md @@ -7,6 +7,8 @@ paths: # Styling Rules +This guide applies to the existing app. For `apps/sim/platform/**`, use `apps/sim/platform/AGENTS.md`. + ## Tailwind 1. **No inline styles** - Use Tailwind classes. Exception: a genuinely dynamic diff --git a/.claude/rules/sim-url-state.md b/.claude/rules/sim-url-state.md index 914bb12403c..110aad206f4 100644 --- a/.claude/rules/sim-url-state.md +++ b/.claude/rules/sim-url-state.md @@ -179,7 +179,7 @@ const setSearch = useDebouncedSearchSetter(write, { debounceMs: 200 }) - **Never write a trimmed value to a param that controls the input** — trimming on write eats the user's trailing space mid-typing and makes multi-word queries untypable. The hook writes the raw value; trim only for the empty-check (the hook does this) and on *read* where the value feeds a query or filter. - **Keep fetches/filtering debounced.** Where the search value feeds a React Query key or an expensive in-memory filter, derive a debounced value off the instant nuqs value (`const debounced = useDebounce(urlSearch, SEARCH_DEBOUNCE_MS)` with `SEARCH_DEBOUNCE_MS` from `@/lib/url-state`) and feed *that* to the query — the instant value is only for the input box. Cheap in-memory filtering over a small static list may read the instant value directly. -- Settings list search boxes use `useSettingsSearch()` from `settings/components/use-settings-search` — the shared `?search=` binding for that surface. +- Settings list search boxes use `useSettingsSearch()` from `@/hooks/use-settings-search` — the shared `?search=` binding for that surface. - Preserve `clearOnDefault` (empty clears the param), the existing default, and `history: 'replace'`. See logs (`use-log-filters.ts` grouped, query stays debounced), integrations (cheap in-memory filter, instant value), and tables (filter stays debounced). ## Sort convention (`sort` + `dir`) diff --git a/.claude/skills/implement-platform b/.claude/skills/implement-platform new file mode 120000 index 00000000000..09d30a82d38 --- /dev/null +++ b/.claude/skills/implement-platform @@ -0,0 +1 @@ +../../.agents/skills/implement-platform \ No newline at end of file diff --git a/.cursor/rules/emcn-components.mdc b/.cursor/rules/emcn-components.mdc index 146915df09d..152cfcbdf52 100644 --- a/.cursor/rules/emcn-components.mdc +++ b/.cursor/rules/emcn-components.mdc @@ -7,6 +7,8 @@ globs: ["packages/emcn/**"] # EMCN Components +This guide applies to the existing app. For `apps/sim/platform/**`, use `apps/sim/platform/AGENTS.md`. + 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`, date field→`ChipDatePicker`). `ChipSwitch` is a segmented choice between options, not a replacement for `Switch`: a boolean on/off toggle stays `Switch`. 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 diff --git a/.cursor/rules/platform-design.mdc b/.cursor/rules/platform-design.mdc new file mode 100644 index 00000000000..d84b752bf06 --- /dev/null +++ b/.cursor/rules/platform-design.mdc @@ -0,0 +1,8 @@ +--- +description: "Sim Platform implementation from Paper" +globs: ["apps/sim/platform/**"] +--- + + + +Use the canonical contract in `apps/sim/platform/AGENTS.md` and the `implement-platform` skill. Legacy EMCN and app styling rules do not govern this separate project. diff --git a/.cursor/rules/sim-architecture.mdc b/.cursor/rules/sim-architecture.mdc index 740e9601df1..a3f69a1605e 100644 --- a/.cursor/rules/sim-architecture.mdc +++ b/.cursor/rules/sim-architecture.mdc @@ -7,6 +7,8 @@ globs: ["apps/sim/**"] # Sim App Architecture +This guide applies to the existing app. For `apps/sim/platform/**`, use `apps/sim/platform/AGENTS.md`. + Repository layout, package boundaries, the application operation boundary, naming, and utils placement are in the root `CLAUDE.md`. This file holds the app-internal boundaries. ## The `'use client'` server boundary diff --git a/.cursor/rules/sim-settings-pages.mdc b/.cursor/rules/sim-settings-pages.mdc index aeb87b3325a..ddb49f8f879 100644 --- a/.cursor/rules/sim-settings-pages.mdc +++ b/.cursor/rules/sim-settings-pages.mdc @@ -340,7 +340,7 @@ rendered through `` from is what keeps tone/icon/variant/tooltip handling from drifting between the two shells. Reach for it before hand-rolling a `Chip`. - **`useSettingsUnsavedGuard({ isDirty, navigationBlocked, onDiscard })`** from - `@/components/settings/use-settings-unsaved-guard` registers one editor with the + `@/hooks/use-settings-unsaved-guard` registers one editor with the shared settings store. Register every full-page draft, including inline creation forms. Dirty computation stays local and derived; fetched defaults stay clean. - **`onDiscard` resets the registered draft.** The root `SettingsNavigationGuard` diff --git a/.cursor/rules/sim-styling.mdc b/.cursor/rules/sim-styling.mdc index 3f00af5afff..aefcc4330eb 100644 --- a/.cursor/rules/sim-styling.mdc +++ b/.cursor/rules/sim-styling.mdc @@ -7,6 +7,8 @@ globs: ["apps/sim/**/*.tsx","apps/sim/**/*.css"] # Styling Rules +This guide applies to the existing app. For `apps/sim/platform/**`, use `apps/sim/platform/AGENTS.md`. + ## Tailwind 1. **No inline styles** - Use Tailwind classes. Exception: a genuinely dynamic diff --git a/.cursor/rules/sim-url-state.mdc b/.cursor/rules/sim-url-state.mdc index cb1d05fa410..7b420a26588 100644 --- a/.cursor/rules/sim-url-state.mdc +++ b/.cursor/rules/sim-url-state.mdc @@ -172,7 +172,7 @@ const setSearch = useDebouncedSearchSetter(write, { debounceMs: 200 }) - **Never write a trimmed value to a param that controls the input** — trimming on write eats the user's trailing space mid-typing and makes multi-word queries untypable. The hook writes the raw value; trim only for the empty-check (the hook does this) and on *read* where the value feeds a query or filter. - **Keep fetches/filtering debounced.** Where the search value feeds a React Query key or an expensive in-memory filter, derive a debounced value off the instant nuqs value (`const debounced = useDebounce(urlSearch, SEARCH_DEBOUNCE_MS)` with `SEARCH_DEBOUNCE_MS` from `@/lib/url-state`) and feed *that* to the query — the instant value is only for the input box. Cheap in-memory filtering over a small static list may read the instant value directly. -- Settings list search boxes use `useSettingsSearch()` from `settings/components/use-settings-search` — the shared `?search=` binding for that surface. +- Settings list search boxes use `useSettingsSearch()` from `@/hooks/use-settings-search` — the shared `?search=` binding for that surface. - Preserve `clearOnDefault` (empty clears the param), the existing default, and `history: 'replace'`. See logs (`use-log-filters.ts` grouped, query stays debounced), integrations (cheap in-memory filter, instant value), and tables (filter stays debounced). ## Sort convention (`sort` + `dir`) diff --git a/.github/CI.md b/.github/CI.md index 15b06c5be45..1831aefbb53 100644 --- a/.github/CI.md +++ b/.github/CI.md @@ -38,6 +38,8 @@ Memory-heavy builds and browser jobs retain appropriately sized Blacksmith runne WebKit acceptance on Blacksmith uses the matching, digest-pinned Playwright browser image for mobile and docs navigation. The app, database fixtures, native modules, and harness stay on the host and retain loopback URLs. Chromium and GitHub fallback keep native browser installation and launch. Update both workspace Playwright dependencies, the image index digest, and the expected client version/revision checks together; verify the installed release's native launch timeout and real WebKit acceptance before changing the pin. +HTTP and docs browser installation first asks the pinned Playwright driver to simulate its OS dependency installation. When the simulation requires no package changes, the runner avoids an apt update; missing dependencies, proposed upgrades, or an unsuccessful simulation use the normal dependency installer. Browser selection and downloads still use the pinned driver, including full native Chromium for extension acceptance. + Every HTTP and browser group consumes the same production build in an isolated job/database. Electron also uses that build and skips its development-route warm-up. Local HTTP and Electron development runs retain their dev-server behavior. Build inputs exclude test-only files so a harness edit can reuse an unchanged production build. Desktop inbox protocol, assistant actions UI, and organization search UI run as three parallel HTTP jobs with isolated databases, apps, and reports. Each harness owns its acceptance task, so an edit selects its own job. The inbox protocol task depends on server and realtime code; the browser tasks also depend on workspace UI. Desktop inbox needs no browser. The UI jobs and browser extension install Chromium while their app starts, waiting for both before executing assertions; browser-install failures still fail the job and preserve the install log. No acceptance assertions are removed by the split. @@ -71,6 +73,8 @@ The current weights are median test durations in seconds from runs [41496623284] Desktop native outputs are keyed by source, build recipes, compiler, SDK, OS, architecture, and Node header bytes. Every build still validates restored checksums, executable modes, universal architectures, and the helper signature; invalid or incomplete outputs rebuild. Native-input pushes to main/staging seed trusted Node 22 and 24 caches without repeating the PR smoke suite. PR cache entries never populate trusted branch caches. +Desktop releases resolve and validate their source once, then check out that immutable commit for packaging and four isolated smoke partitions. The release smoke partitions reuse the existing whole-file duration sharder on the release's Node 22 toolchain and run alongside signing and notarization. Historical tags without the sharder retain one complete smoke run, and native compilation caching is optional when the checked-out revision predates it. Packaging retains source checks, package validation, and packaged Electron smoke. Publication waits for packaging and every smoke partition, downloads the packaging job's immutable artifact ID from the same run, verifies any existing release assets, and uploads the updater manifest last. Artifact-only runs never publish. + Each shard uploads its coverage/timing report, the complete discovery report, and its deterministic shard plan. `bun run test:scripts scripts/duration-shards.test.ts scripts/desktop-e2e.test.ts` checks partition coverage, unknown files, deterministic ordering, and balancing. The CLI regression executes tiny browser-free Playwright fixtures to prove that imported test factories and paths containing regex characters are selected correctly; it launches no Electron or browser. Local pushes run `lint:check` and `type-check` through Husky before Git sends commits. These gates reuse Turborepo's task cache; a failure stops the push. The pre-commit hook still formats staged files, and hosted CI owns tests, audits, builds, and acceptance checks. diff --git a/.github/scripts/ci-plan.gql b/.github/scripts/ci-plan.gql index 78dcbf3eaab..c24dc7153b3 100644 --- a/.github/scripts/ci-plan.gql +++ b/.github/scripts/ci-plan.gql @@ -1,24 +1,59 @@ -query($base: String!, $head: String!) { - packages { items { name path } } - affectedTasks(base: $base, head: $head, tasks: [ - "test", "build", "type-check", "ci:integration", "ci:desktop-live", - "ci:e2e-scim", "ci:e2e-cli", "ci:e2e-stop-after", "ci:e2e-desktop-inbox", - "ci:e2e-assistant-actions", "ci:e2e-organization-search", - "ci:e2e-docs-platform", - "ci:e2e-browser-extension", "ci:e2e-project-files", "ci:e2e-mobile", "ci:e2e-seo" - ]) { +query ($base: String!, $head: String!) { + packages { + items { + name + path + } + } + affectedTasks( + base: $base + head: $head + tasks: [ + "test" + "build" + "type-check" + "ci:integration" + "ci:desktop-live" + "ci:e2e-scim" + "ci:e2e-cli" + "ci:e2e-stop-after" + "ci:e2e-desktop-inbox" + "ci:e2e-assistant-actions" + "ci:e2e-organization-search" + "ci:e2e-docs-platform" + "ci:e2e-browser-extension" + "ci:e2e-project-files" + "ci:e2e-mobile" + "ci:e2e-seo" + ] + ) { items { name fullName - package { name } + package { + name + } reason { __typename - ... on TaskFileChanged { filePath } - ... on TaskGlobalFileChanged { filePath } - ... on TaskGlobalDepsChanged { filePath } - ... on TaskDependencyTaskChanged { taskName packageName } - ... on TaskPackageDependencyChanged { packageName } - ... on TaskAllChanged { description } + ... on TaskFileChanged { + filePath + } + ... on TaskGlobalFileChanged { + filePath + } + ... on TaskGlobalDepsChanged { + filePath + } + ... on TaskDependencyTaskChanged { + taskName + packageName + } + ... on TaskPackageDependencyChanged { + packageName + } + ... on TaskAllChanged { + description + } } } } diff --git a/.github/scripts/http-e2e.sh b/.github/scripts/http-e2e.sh index e05cfbf3d09..c6bfea882d6 100755 --- a/.github/scripts/http-e2e.sh +++ b/.github/scripts/http-e2e.sh @@ -3,7 +3,7 @@ # CI groups, which reuse the production build from `e2e-build`. # The job extracts it into apps/sim/.next before running these suites. # -# Usage: http-e2e.sh (run from apps/sim) +# Usage: http-e2e.sh (run from apps/sim) # # The job provides DATABASE_URL, BETTER_AUTH_SECRET and ENCRYPTION_KEY; each group sets the rest of # its app's environment here. Reports and server logs land in $RUNNER_TEMP/e2e. @@ -20,7 +20,7 @@ # telemetry flush runs detached and still writes .next/dev). set -euo pipefail -group=${1:?usage: http-e2e.sh } +group=${1:?usage: http-e2e.sh } report_dir="$RUNNER_TEMP/e2e" ready_timeout_seconds=600 mkdir -p "$report_dir" @@ -32,6 +32,10 @@ status_log='' realtime_pid='' realtime_tag='' realtime_log='' +platform_pid='' +platform_tag='' +platform_log='' +agent_pid='' browsers_pid='' local_app_url='' # `dev` or `start`; a group sets it before start_app. @@ -63,6 +67,17 @@ finish() { tail -n 100 "$realtime_log" fi fi + if [ -n "$platform_pid" ]; then + bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$platform_pid" "$platform_tag" || status=1 + wait "$platform_pid" 2>/dev/null || true + if [ "$status" -ne 0 ]; then + tail -n 100 "$platform_log" + fi + fi + if [ -n "$agent_pid" ]; then + kill "$agent_pid" 2>/dev/null || true + wait "$agent_pid" 2>/dev/null || true + fi if [ -n "$browsers_pid" ]; then kill -- "-$browsers_pid" 2>/dev/null || true wait "$browsers_pid" 2>/dev/null || true @@ -75,7 +90,7 @@ start_browser_install() { local name=$1 shift browsers_log="$report_dir/$name-playwright-install.log" - setsid --wait bunx --no-install playwright install --with-deps "$@" > "$browsers_log" 2>&1 & + setsid --wait bash "$GITHUB_WORKSPACE/.github/scripts/install-playwright.sh" "$@" > "$browsers_log" 2>&1 & browsers_pid=$! } @@ -133,6 +148,72 @@ start_realtime() { wait_ready "$realtime_pid" "http://127.0.0.1:$port/health" "$name realtime server" } +# stop_app: stops the app start_app started, so a group can start it again under other flags. +stop_app() { + bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$app_tag" + wait "$server_pid" 2>/dev/null || true + server_pid='' +} + +# start_platform : the production build of apps/sim/platform, behind the main app's rewrite. +start_platform() { + local port=$1 + platform_tag="platform-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$" + platform_log="$report_dir/platform-live-platform.log" + E2E_APP="$platform_tag" SIM_PLATFORM_API_ORIGIN="$local_app_url" SIM_PLATFORM_BASE_PATH=/org setsid node \ + ../../node_modules/next/dist/bin/next start platform --hostname 127.0.0.1 --port "$port" \ + > "$platform_log" 2>&1 & + platform_pid=$! + wait_ready "$platform_pid" "http://127.0.0.1:$port/org" 'Platform zone' +} + +# platform_live_environment: one hosted Enterprise organization, the deterministic seed its specs +# read, and a scripted worker in place of the model. Hosted is decided by the app's hostname, which +# the job maps to loopback. Google and Microsoft answer only to real credentials, so those clients +# are fixtures, and what no fixture can reach is reported by the specs as a skip. +platform_live_environment() { + app_server=start + export REDIS_URL=redis://127.0.0.1:6379 + export E2E_APP_PUBLIC_URL=http://platform-live.sim.ai:3027 + export NEXT_PUBLIC_APP_URL="$E2E_APP_PUBLIC_URL" + export INTERNAL_API_BASE_URL=http://127.0.0.1:3027 TRUSTED_ORIGINS=http://127.0.0.1:3027 + export BILLING_ENABLED=true NEXT_PUBLIC_BILLING_ENABLED=true + export ENTERPRISE_ENABLED=true NEXT_PUBLIC_ENTERPRISE_ENABLED=true + export SSO_ENABLED=true NEXT_PUBLIC_SSO_ENABLED=true + export SCIM_ENABLED=true NEXT_PUBLIC_SCIM_ENABLED=true + export ORGANIZATIONS_ENABLED=true NEXT_PUBLIC_ORGANIZATIONS_ENABLED=true + export KNOWLEDGE_MEMBER_ACCESS=true CREDENTIAL_GROUPS=true ACCESS_CONTROL_ENABLED=true + export NEXT_PUBLIC_ACCESS_CONTROL_ENABLED=true + export SIM_LINKS=true PEOPLE=true + export INTERNAL_API_SECRET=platform-live-ci-local-secret-at-least-32-characters + export COPILOT_API_KEY=platform-live-ci-local-copilot-key MOTHERSHIP_SIM_TRANSPORT=direct + export SIM_AGENT_API_URL=http://127.0.0.1:3029 + export GOOGLE_CLIENT_ID=platform-live-google-client GOOGLE_CLIENT_SECRET=platform-live-google-secret + export MICROSOFT_CLIENT_ID=platform-live-microsoft-client MICROSOFT_CLIENT_SECRET=platform-live-microsoft-secret + export STRIPE_SECRET_KEY=sk_test_platform_live STRIPE_WEBHOOK_SECRET=whsec_platform_live + + export PLATFORM_LIVE_ORIGIN="$E2E_APP_PUBLIC_URL" + export PLATFORM_LIVE_PASSWORD=platform-live-password-1 + export PLATFORM_LIVE_ORGANIZATION_ID=platform-live-organization + export PLATFORM_LIVE_ORGANIZATION_NAME='Sim Live Org' + export PLATFORM_LIVE_OWNER_EMAIL=olive@platform-live.test + export PLATFORM_LIVE_EMAIL=ada@platform-live.test + export PLATFORM_LIVE_MEMBER_EMAIL=mia@platform-live.test + export PLATFORM_LIVE_CONNECTED_EMAIL=cory@platform-live.test + export PLATFORM_LIVE_ROLE_TARGET_EMAIL=rae@platform-live.test + export PLATFORM_LIVE_REMOVABLE_EMAIL=remy@platform-live.test + export PLATFORM_LIVE_REFUSED_INVITE_EMAIL=otto@platform-live.test + export PLATFORM_LIVE_ORCHESTRATOR=1 + bun --no-env-file scripts/platform-live-seed.ts +} + +# run_platform_live +run_platform_live() { + local project=$1 name=$2 + PLATFORM_LIVE_REPORT_PATH="$report_dir/$name/report.json" \ + bun run test:platform:live --project="$project" ${PLATFORM_LIVE_GREP:+--grep "$PLATFORM_LIVE_GREP"} +} + # wait_ready