Skip to content

Commit aa2b24f

Browse files
committed
Merge remote-tracking branch 'origin/staging' into fix/agiloft-connector
2 parents f173166 + ec70c4d commit aa2b24f

14 files changed

Lines changed: 300 additions & 204 deletions

File tree

apps/docs/app/global.css

Lines changed: 161 additions & 140 deletions
Large diffs are not rendered by default.

apps/docs/openapi-v2-billing.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@
248248
"type": "apiKey",
249249
"in": "header",
250250
"name": "X-API-Key",
251-
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
251+
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description."
252252
}
253253
},
254254
"headers": {

apps/docs/openapi-v2-files-audit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1952,7 +1952,7 @@
19521952
"type": "apiKey",
19531953
"in": "header",
19541954
"name": "X-API-Key",
1955-
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
1955+
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description."
19561956
}
19571957
},
19581958
"headers": {

apps/docs/openapi-v2-knowledge.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1954,7 +1954,7 @@
19541954
"type": "apiKey",
19551955
"in": "header",
19561956
"name": "X-API-Key",
1957-
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
1957+
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description."
19581958
}
19591959
},
19601960
"headers": {

apps/docs/openapi-v2-logs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@
363363
"type": "apiKey",
364364
"in": "header",
365365
"name": "X-API-Key",
366-
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
366+
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description."
367367
}
368368
},
369369
"headers": {

apps/docs/openapi-v2-resources.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2016,7 +2016,7 @@
20162016
"type": "apiKey",
20172017
"in": "header",
20182018
"name": "X-API-Key",
2019-
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
2019+
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description."
20202020
}
20212021
},
20222022
"headers": {

apps/docs/openapi-v2-tables.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3666,7 +3666,7 @@
36663666
"type": "apiKey",
36673667
"in": "header",
36683668
"name": "X-API-Key",
3669-
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
3669+
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description."
36703670
}
36713671
},
36723672
"headers": {

apps/docs/openapi-v2-workflows.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2048,7 +2048,7 @@
20482048
"type": "apiKey",
20492049
"in": "header",
20502050
"name": "X-API-Key",
2051-
"description": "Your Sim API key, personal or workspace-scoped. Generate one from the Sim dashboard under Settings > API Keys. A workspace API key is not accepted everywhere: operations that act on behalf of a specific human — administrative reads, secret access, and irreversible or governance-affecting writes — always reject it, whatever role the key carries. Each such operation says so in its own description, and the rejection surfaces as `403` unless the operation conceals unauthorized resources, in which case it is reported as `404`. Use a personal API key for those."
2051+
"description": "Your Sim API key, personal or workspace-scoped. Generate one under Settings > API Keys. Operations that reject workspace keys say so in their own description."
20522052
}
20532053
},
20542054
"headers": {

apps/sim/app/_shell/providers/posthog-provider.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,28 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
4242
password: true,
4343
email: false,
4444
},
45+
/**
46+
* None of these nodes are painted, so replay fidelity is
47+
* unchanged, while each full snapshot serializes fewer nodes on
48+
* the main thread and ships a smaller payload.
49+
*
50+
* Enumerated rather than `true`/`'all'` on purpose — those
51+
* presets also enable `headTitleMutations`, which would drop
52+
* `document.title` changes and lose the page identity a replay
53+
* viewer reads while scrubbing.
54+
*/
55+
slimDOMOptions: {
56+
script: true,
57+
comment: true,
58+
headFavicon: true,
59+
headWhitespace: true,
60+
headMetaDescKeywords: true,
61+
headMetaSocial: true,
62+
headMetaRobots: true,
63+
headMetaHttpEquiv: true,
64+
headMetaAuthorship: true,
65+
headMetaVerification: true,
66+
},
4567
recordCrossOriginIframes: false,
4668
recordHeaders: false,
4769
recordBody: false,

apps/sim/app/workspace/[workspaceId]/prefetch.ts

Lines changed: 72 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { createLogger } from '@sim/logger'
2+
import { getErrorMessage } from '@sim/utils/errors'
13
import type { QueryClient } from '@tanstack/react-query'
24
import { listWorkspacesContract, type WorkspaceHostContext } from '@/lib/api/contracts/workspaces'
35
import { listMothershipChats } from '@/lib/copilot/chat/list-mothership-chats'
@@ -21,10 +23,7 @@ import {
2123
import { FOLDER_LIST_STALE_TIME, folderKeys, mapFolder } from '@/hooks/queries/utils/folder-keys'
2224
import { workflowKeys } from '@/hooks/queries/utils/workflow-keys'
2325
import { mapWorkflow, WORKFLOW_LIST_STALE_TIME } from '@/hooks/queries/utils/workflow-list-query'
24-
import {
25-
normalizeWorkspacesResponse,
26-
WORKSPACE_LIST_STALE_TIME,
27-
} from '@/hooks/queries/utils/workspace-list-query'
26+
import { normalizeWorkspacesResponse } from '@/hooks/queries/utils/workspace-list-query'
2827
import { WORKSPACE_PERMISSIONS_STALE_TIME, workspaceKeys } from '@/hooks/queries/workspace'
2928
import {
3029
WORKSPACE_HOST_CONTEXT_STALE_TIME,
@@ -47,22 +46,81 @@ export function prefetchWorkspaceHostContext(
4746
})
4847
}
4948

49+
const logger = createLogger('WorkspacePrefetch')
50+
51+
/**
52+
* Seeds the viewer's workspace list, which the switcher reads.
53+
*
54+
* Seeded rather than prefetched so the empty-list case can decline to create a
55+
* cache entry at all: the route's default-workspace creation path must run on
56+
* the client, and an entry — even an empty one — would suppress it. Expressing
57+
* that as an absent seed keeps a normal state out of the error channel, where
58+
* it previously cost a full second re-read (`retry: 1`) to re-derive an outcome
59+
* already known.
60+
*/
61+
async function seedWorkspaceList(
62+
queryClient: QueryClient,
63+
userId: string,
64+
activeOrganizationId: string | null
65+
): Promise<void> {
66+
try {
67+
const payload = await listWorkspacesForViewer({
68+
userId,
69+
activeOrganizationId,
70+
scope: 'active',
71+
})
72+
if (payload.workspaces.length === 0) return
73+
/**
74+
* Parsing through the route contract's response schema strips the same
75+
* server-only fields `requestJson` strips on the client, guaranteeing the
76+
* seeded shape is identical to a client fetch.
77+
*/
78+
queryClient.setQueryData(
79+
workspaceKeys.list('active'),
80+
normalizeWorkspacesResponse(listWorkspacesContract.response.schema.parse(payload))
81+
)
82+
} catch (error) {
83+
/**
84+
* Swallowed rather than rethrown — this read is an optimization; the layout
85+
* renders fine without it and the client fetch reaches the route instead.
86+
* Logged because contract drift between the read and the response schema
87+
* would otherwise degrade silently into every viewer waterfalling.
88+
*/
89+
logger.warn('Workspace list seed failed; client will fetch', {
90+
error: getErrorMessage(error),
91+
})
92+
}
93+
}
94+
5095
/**
5196
* Prefetches the sidebar's workflow, chat, folder, workspace-permissions,
5297
* workspace, and viewer-profile reads for a workspace and stores them under the
5398
* same query keys + mappers the client hooks use, so the persistent sidebar
54-
* (including the workspace switcher header and the footer's profile row) paints
55-
* populated on the first server render
56-
* instead of flashing skeletons on a cold load (e.g. after the browser
57-
* discards an idle tab). Calls the data layer directly — the same functions
58-
* the API routes use — with no internal HTTP hop.
99+
* (including the workspace switcher header and the footer's profile row) is
100+
* populated without a client-side request waterfall on a cold load (e.g. after
101+
* the browser discards an idle tab). Calls the data layer directly — the same
102+
* functions the API routes use — with no internal HTTP hop.
59103
*
60104
* The host context is the authorization proof for this server-render pass, so
61105
* permission prefetch can reuse its effective permission without repeating
62106
* workspace and membership reads. It also proves the viewer has at least one
63-
* accessible workspace, which is why the workspace-list prefetch can safely
64-
* skip the route's empty-list default-workspace creation path — and the
65-
* route's orphaned-workflow repair, which still runs on client refetches.
107+
* accessible workspace, so this pass skips the route's orphaned-workflow
108+
* repair, which still runs on client refetches.
109+
*
110+
* All reads run concurrently and are awaited together, so every pane is settled
111+
* in the cache before `dehydrate` and the sidebar still paints populated rather
112+
* than flashing skeletons that stream in behind the shell.
113+
*
114+
* The workspace list is seeded rather than prefetched. An empty or failed read
115+
* seeds nothing, leaving the client fetch to reach `GET /api/workspaces`'
116+
* default-workspace creation path — the same outcome a rejecting `queryFn` used
117+
* to produce, without routing a normal state through the error channel. That
118+
* matters because `makeQueryClient` dehydrates pending queries and sets
119+
* `retryOnMount: false`: were this read ever deferred, its rejection would
120+
* hydrate the client query into an error state nothing retries, permanently
121+
* locking a brand-new viewer out of workspace creation. Seeding also skips the
122+
* `retry: 1` default, which previously ran the whole read a second time, a
123+
* retry delay later, purely to re-derive an outcome already known.
66124
*/
67125
export async function prefetchWorkspaceSidebar(
68126
queryClient: QueryClient,
@@ -72,6 +130,7 @@ export async function prefetchWorkspaceSidebar(
72130
activeOrganizationId: string | null
73131
): Promise<void> {
74132
if (hostContext.workspace.id !== workspaceId) return
133+
75134
await Promise.all([
76135
queryClient.prefetchQuery({
77136
queryKey: workflowKeys.list(workspaceId, 'active'),
@@ -101,27 +160,6 @@ export async function prefetchWorkspaceSidebar(
101160
},
102161
staleTime: FOLDER_LIST_STALE_TIME,
103162
}),
104-
queryClient.prefetchQuery({
105-
queryKey: workspaceKeys.list('active'),
106-
queryFn: async () => {
107-
const payload = await listWorkspacesForViewer({
108-
userId,
109-
activeOrganizationId,
110-
scope: 'active',
111-
})
112-
// An empty list means GET /api/workspaces' default-workspace creation
113-
// path must run — throw so prefetchQuery caches nothing and the client
114-
// fetch reaches the route.
115-
if (payload.workspaces.length === 0) {
116-
throw new Error('Empty workspace list requires the route creation path')
117-
}
118-
// Parsing through the route contract's response schema strips the same
119-
// server-only fields `requestJson` strips on the client, guaranteeing the
120-
// cached shape is identical to a client fetch.
121-
return normalizeWorkspacesResponse(listWorkspacesContract.response.schema.parse(payload))
122-
},
123-
staleTime: WORKSPACE_LIST_STALE_TIME,
124-
}),
125163
queryClient.prefetchQuery({
126164
queryKey: workspaceKeys.permissions(workspaceId),
127165
queryFn: () =>
@@ -148,5 +186,6 @@ export async function prefetchWorkspaceSidebar(
148186
},
149187
staleTime: USER_PROFILE_STALE_TIME,
150188
}),
189+
seedWorkspaceList(queryClient, userId, activeOrganizationId),
151190
])
152191
}

0 commit comments

Comments
 (0)