Skip to content

Commit 0caccb4

Browse files
Merge remote-tracking branch 'origin/staging' into feat/api-key-cred-group
# Conflicts: # apps/sim/lib/credential-groups/application/public-enrollment.test.ts
2 parents 2158fa2 + 452e542 commit 0caccb4

125 files changed

Lines changed: 4716 additions & 568 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/rules/sim-url-state.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ These reads/mutations are **not** anti-patterns and stay as-is:
4646
- **Route navigations** — `router.push('/path/[id]?folderId=x')` that changes the route *path*, not just the current query. A nuqs setter only mutates the query on the current path; cross-path navigation stays on `router`.
4747
- **Read-once auth / redirect signals** — `token`, `callbackUrl`, `redirect`, `error`, `invite_flow`, `new` (invite signup flow), `upgraded`, `redirect_workflow`, etc. These are navigation signals consumed once (often read-then-strip), not synced view-state. Leave them on `useSearchParams`. Key names are per-surface: files' `new` is a genuine nuqs param (`files/search-params.ts`), while invite's `new` is a one-shot signup signal.
4848

49+
### Remembered list-preference exception
50+
51+
Files, Tables, and Knowledge may persist their last-used filter/sort snapshot through
52+
`useResourceListPreferences`. This is a fallback preference, not a second live source of truth:
53+
54+
- nuqs remains authoritative while the module is open.
55+
- Zustand is consulted once on a clean module entry, after persisted state hydrates.
56+
- An explicit URL filter/sort parameter wins even when it resolves to the module default. The
57+
complete resolved URL snapshot becomes the remembered value; omitted fields use URL defaults
58+
rather than merging with storage.
59+
- Explicit filter/sort gestures commit the same complete snapshot to nuqs and Zustand together.
60+
- Never mirror subsequent URL changes with a synchronization effect or `popstate` listener.
61+
- Search and folder navigation remain URL-only and are excluded from the persisted snapshot.
62+
4963
## Per-feature `search-params.ts` — single source of truth
5064

5165
Co-locate a `search-params.ts` next to the feature. Export the parser map (and shared options). Both the client (`useQueryStates`/`useQueryState`) and any server component (`createSearchParamsCache` from `nuqs/server`) import from this one file. Import parsers from `nuqs/server` so the module is safe to import in both client and server contexts.

‎.github/workflows/test-build.yml‎

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,17 @@ jobs:
182182
# without `--coverage`. See the Codecov note below.
183183
#
184184
# apps/sim runs only its first shard here; `test-shard` below runs the
185-
# other. That suite is bound by the single Vite server thread that feeds
185+
# others. That suite is bound by the single Vite server thread that feeds
186186
# every worker — wall time is flat from 4 to 13 workers — so a bigger
187-
# runner buys nothing and a second runner halves it.
187+
# runner buys nothing and each extra runner takes a proportional slice.
188188
- name: Run tests
189189
env:
190190
NODE_OPTIONS: '--no-warnings --max-old-space-size=8192'
191191
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
192192
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
193193
ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000' # dummy key for CI only
194194
TURBO_CACHE_DIR: .turbo
195-
SIM_TEST_SHARD: 1/2
195+
SIM_TEST_SHARD: 1/3
196196
run: bun run test
197197

198198
- name: Check schema and migrations are in sync
@@ -208,15 +208,20 @@ jobs:
208208
fi
209209
echo "✅ Schema and migrations are in sync"
210210
211-
# The second half of apps/sim's Vitest suite. Everything else — lint, the
212-
# audits, type-check, the other workspaces' suites — lives in `test-build`
213-
# with shard 1; this job exists only because that suite cannot go faster on
214-
# one machine (see the "Run tests" note there). The Turbo cache disk gets
215-
# its own key so the two shards' entries do not evict each other.
211+
# The remaining shards of apps/sim's Vitest suite. Everything else — lint,
212+
# the audits, type-check, the other workspaces' suites — lives in
213+
# `test-build` with shard 1; these jobs exist only because that suite cannot
214+
# go faster on one machine (see the "Run tests" note there). Three shards
215+
# put each runner at roughly the fixed cost of checkout + install. The Turbo
216+
# cache disk gets its own key so the shards' entries do not evict each other.
216217
test-shard:
217-
name: Test (shard 2)
218+
name: Test (shard ${{ matrix.shard }})
218219
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
219220
timeout-minutes: 15
221+
strategy:
222+
fail-fast: false
223+
matrix:
224+
shard: [2, 3]
220225

221226
steps:
222227
- name: Checkout code
@@ -250,7 +255,7 @@ jobs:
250255
uses: ./.github/actions/cache-mount
251256
with:
252257
provider: ${{ vars.CI_PROVIDER }}
253-
key: ${{ github.repository }}-turbo-cache-shard-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
258+
key: ${{ github.repository }}-turbo-cache-shard-${{ matrix.shard }}-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
254259
path: ./.turbo
255260

256261
- name: Install dependencies
@@ -259,14 +264,14 @@ jobs:
259264
- name: Install ripgrep
260265
run: command -v rg || (sudo apt-get update && sudo apt-get install -y ripgrep)
261266

262-
- name: Run tests (apps/sim shard 2/2)
267+
- name: Run tests (apps/sim shard ${{ matrix.shard }}/3)
263268
env:
264269
NODE_OPTIONS: '--no-warnings --max-old-space-size=8192'
265270
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
266271
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
267272
ENCRYPTION_KEY: '0000000000000000000000000000000000000000000000000000000000000000' # dummy key for CI only
268273
TURBO_CACHE_DIR: .turbo
269-
SIM_TEST_SHARD: 2/2
274+
SIM_TEST_SHARD: ${{ matrix.shard }}/3
270275
run: bunx turbo run test --filter=@sim/app
271276

272277
# Next.js production build, in parallel with lint + tests. Sticky disks are

‎apps/docs/content/docs/integrations/file.mdx‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The File block is a built-in Sim block for working with files stored in the work
1616
With the File block, you can:
1717

1818
- **Read and extract content**: Load workspace file objects and extract their text content
19-
- **Search workspace content**: Find literal text across indexed active workspace files with bounded line-level results
19+
- **Search workspace content**: Match a regular expression, or an exact piece of text, against the indexed lines of active workspace files with bounded line-level results
2020
- **Fetch from URLs**: Retrieve and parse files from external URLs with custom headers
2121
- **Write and append**: Create new workspace files or append content to existing ones
2222
- **Compress and decompress**: Bundle files into a .zip archive or extract an archive into the workspace
@@ -70,13 +70,14 @@ Extract the text content of one or more workspace files from selected file objec
7070

7171
### File Search
7272

73-
Search indexed text across active workspace files using literal smart-case substring matching.
73+
Search the indexed text of active workspace files for lines matching a regular expression, and return each matching line once with its file ID and line number. Coverage is what the index currently holds, so check "complete" and "indexStatus" before concluding that something is absent.
7474

7575
#### Input
7676

7777
| Parameter | Type | Required | Description |
7878
| --------- | ---- | -------- | ----------- |
79-
| `query` | string | Yes | Literal text to find \(3-512 characters\). Uppercase Unicode letters make matching case-sensitive. |
79+
| `query` | string | Yes | A regular expression matched against each line, 3-512 characters. Supports "." "*" "+" "?" "\{n,m\}" and their lazy forms, character classes such as "\[a-z\]" and "\[^0-9\]", the classes \d \w \s and \D \W \S, alternation "\|", groups "\(...\)" and "\(?:...\)", the anchors "^" and "$", and the word boundary \b. Lookahead, lookbehind, backreferences, named groups, inline flags such as "\(?i\)", \p\{...\} and POSIX "\[\[:alpha:\]\]" classes are not supported, and a pattern cannot span a line break. The pattern must contain at least 3 consecutive literal characters that every match will include — write "error \d+" rather than "\w+ \d+". Escape any metacharacter you mean literally. Matching is case-insensitive until the pattern contains an uppercase letter you are searching for; uppercase inside an escape or a character class, such as \D or \[A-Z\], does not make it case-sensitive. When the workflow builder sets Match to exact instead, the query is matched verbatim and no metacharacter needs escaping. |
80+
| `mode` | string | No | How the query is read, chosen by the workflow builder: "regex" \(default\) as a regular expression, or "exact" as verbatim text. |
8081
| `maxResults` | number | No | Hard result cap configured by the workflow builder \(1-200, default 50\). |
8182

8283
#### Output

‎apps/docs/content/docs/workflows/blocks/agent.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Live tool-call chips stream for **OpenAI, Anthropic, Azure Anthropic, Google, Ve
109109
| Provider | Streamed thinking | Models |
110110
|----------|-------------------|--------|
111111
| OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5-pro`, `gpt-5.5`, `gpt-5.4-pro`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.2-pro`, `gpt-5.2`, `gpt-5.1`, `gpt-5-pro`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `o4-mini`, `o3`, `o3-mini`, `o1` |
112-
| Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `claude-fable-5`, `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-opus-4-1`, `claude-sonnet-4-5`, `claude-haiku-4-5` |
112+
| Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `claude-fable-5-1`, `claude-fable-5`, `claude-sonnet-5`, `claude-opus-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-opus-4-1`, `claude-sonnet-4-5`, `claude-haiku-4-5` |
113113
| Azure OpenAI | Summaries only — Requires OpenAI organization verification; falls back to no summaries. | `azure/gpt-5.4`, `azure/gpt-5.4-mini`, `azure/gpt-5.4-nano`, `azure/gpt-5.2`, `azure/gpt-5.1`, `azure/gpt-5.1-codex`, `azure/gpt-5`, `azure/gpt-5-mini`, `azure/gpt-5-nano`, `azure/o3`, `azure/o4-mini` |
114114
| Azure Anthropic | Summaries only — These generations omit full thinking; Sim requests summarized thinking on streaming runs. | `azure-anthropic/claude-opus-4-6`, `azure-anthropic/claude-opus-4-5`, `azure-anthropic/claude-sonnet-4-5`, `azure-anthropic/claude-opus-4-1`, `azure-anthropic/claude-haiku-4-5` |
115115
| Google | Summaries only | `gemini-3.6-flash`, `gemini-3.5-flash-lite`, `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` |

‎apps/docs/package.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
"devDependencies": {
4343
"@sim/tsconfig": "workspace:*",
4444
"@tailwindcss/postcss": "^4.0.12",
45-
"@types/mdx": "^2.0.13",
4645
"@types/node": "24.2.1",
4746
"@types/react": "^19.1.2",
4847
"@types/react-dom": "^19.0.4",

‎apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/code-languages.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @vitest-environment jsdom
2+
* @vitest-environment node
33
*
44
* Guards against drift between the code-block language picker and the Prism grammars actually
55
* registered by CodeBlockHighlight: every selectable language must have a registered grammar, or it

‎apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/collaboration/apply-streamed-markdown.test.ts‎

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -157,41 +157,6 @@ describe('agent-stream applier', () => {
157157
expect(freshText).toContain('Gamma paragraph')
158158
})
159159

160-
it('preserves a concurrent peer edit to a region the agent snapshot does not include', () => {
161-
// This is the core "AI as a CRDT peer" guarantee: the agent relays only its OWN delta (computed
162-
// against a private shadow), never a whole-document reconcile that would revert a peer's edit.
163-
const { editor, doc } = track(makeCollabEditor())
164-
165-
const session = beginAgentStream(editor)!
166-
applyAgentStreamFrame(editor, session, 'Alpha paragraph.\n\nBeta paragraph.')
167-
168-
// A peer edits the FIRST paragraph directly on the shared doc — the agent's later snapshot still
169-
// carries the ORIGINAL first paragraph (it was built from the base, before this edit).
170-
const peer = new Y.Doc()
171-
Y.applyUpdate(peer, Y.encodeStateAsUpdate(doc))
172-
const peerFrag = peer.getXmlFragment('default')
173-
peer.transact(() => {
174-
const firstPara = peerFrag.get(0) as Y.XmlElement
175-
const textNode = firstPara.get(0) as Y.XmlText
176-
textNode.insert(textNode.toString().length, ' EDITED')
177-
})
178-
Y.applyUpdate(doc, Y.encodeStateAsUpdate(peer, Y.encodeStateVector(doc)))
179-
peer.destroy()
180-
181-
// The agent appends a third paragraph. Its snapshot's first paragraph is the stale original, but the
182-
// shadow-relayed delta only inserts the new paragraph — so the peer's " EDITED" must survive.
183-
applyAgentStreamFrame(
184-
editor,
185-
session,
186-
'Alpha paragraph.\n\nBeta paragraph.\n\nGamma paragraph.'
187-
)
188-
endAgentStream(session)
189-
190-
const live = doc.getXmlFragment('default').toString()
191-
expect(live).toContain('EDITED')
192-
expect(live).toContain('Gamma paragraph')
193-
})
194-
195160
it('reuses cached binding metadata across frames, still emitting minimal per-frame deltas', () => {
196161
// The binding `meta` is built ONCE (first frame) and reused — `updateYFragment` maintains it in place,
197162
// so we skip an O(doc) `initProseMirrorDoc` rebuild per frame. This guards that caching preserves the

‎apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image-drag-move.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @vitest-environment jsdom
2+
* @vitest-environment node
33
*
44
* Dragging an image to reposition it inside a document must MOVE it, not import it again.
55
*

‎apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/image.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* @vitest-environment jsdom
2+
* @vitest-environment node
33
*/
44
import { describe, expect, it } from 'vitest'
55
import {

‎apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-parse.test.ts‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @vitest-environment jsdom
33
*/
44
import { Editor } from '@tiptap/core'
5-
import { afterEach, describe, expect, it } from 'vitest'
5+
import { afterAll, describe, expect, it } from 'vitest'
66
import { createMarkdownContentExtensions } from './extensions'
77
import { parseMarkdownToDoc, serializeMarkdownBody, splitMarkdownBlocks } from './markdown-parse'
88
import { isRoundTripSafe } from './round-trip-safety'
@@ -12,19 +12,20 @@ const isEmptyPara = (n: { type?: string; content?: unknown[] }): boolean =>
1212
n.type === 'paragraph' && !n.content?.length
1313

1414
let editor: Editor | null = null
15-
afterEach(() => {
15+
afterAll(() => {
1616
editor?.destroy()
1717
editor = null
1818
})
1919

20-
/** The current whole-document path: parse markdown in one shot, serialize back. */
20+
/**
21+
* The current whole-document path: parse markdown in one shot, serialize back. One editor serves
22+
* every call — `setContent` replaces the document wholesale, so a fresh instance per call only adds
23+
* the cost of building the view, which the property tests below paid hundreds of times over.
24+
*/
2125
function oneShot(body: string): string {
22-
editor = new Editor({ extensions: createMarkdownContentExtensions() })
26+
editor ??= new Editor({ extensions: createMarkdownContentExtensions() })
2327
editor.commands.setContent(body, { contentType: 'markdown' })
24-
const out = editor.getMarkdown()
25-
editor.destroy()
26-
editor = null
27-
return out
28+
return editor.getMarkdown()
2829
}
2930

3031
/**

0 commit comments

Comments
 (0)