Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2319,9 +2319,9 @@ function ServiceAccountConnectDisplay({
const connected = locallyConnected

// Creating a credential mutates the workspace — hide it from read-only
// members, and honour the provider's own preview gate (custom Slack bots
// ride the slack_v2 flag) so chat can't surface what the integrations page
// deliberately hides.
// members, and honor the owning block's visibility (custom Slack bots follow
// slack_v2) so chat can't surface what the integrations page deliberately
// hides.
if (!target || target.hidden || !canEdit || !workspaceId) return null

const label = reconnectCredentialId
Expand Down
46 changes: 27 additions & 19 deletions apps/sim/blocks/registry-lookup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,33 @@ describe('getBlock prototype safety', () => {
/**
* The list and the detail read must agree about a type.
*
* `slack_v2` is `preview`-gated while `slack` v1 deliberately stays in the
* toolbar so a workspace has a Slack block during the gate. Resolving the
* detail to the newest version and then hiding it answered `404` for a type
* `GET /api/v2/blocks` was publishing in the same breath.
* A preview successor can be hidden while its released base version remains in
* the toolbar. Resolving the detail to the newest version and then hiding it
* would answer `404` for a type `GET /api/v2/blocks` publishes in the same
* breath. Once the successor is released, both surfaces must resolve to it.
*/
describe('version resolution for a viewer', () => {
it.each(['slack', 'table'])(
'resolves %s to a version the unrevealed viewer can actually see',
async (type) => {
const { getLatestBlockForViewer, getAllBlocks } = await import('@/blocks/registry')

const detail = getLatestBlockForViewer(type)
const listed = getAllBlocks().find(
(block) =>
!block.hideFromToolbar && (block.type === type || block.type.startsWith(`${type}_v`))
)

expect(Boolean(detail)).toBe(Boolean(listed))
if (detail && listed) expect(detail.type).toBe(listed.type)
}
)
it('falls back to the released table block while table_v2 is unrevealed', async () => {
const { getLatestBlockForViewer, getAllBlocks } = await import('@/blocks/registry')
const detail = getLatestBlockForViewer('table')
const listed = getAllBlocks().find(
(block) =>
!block.hideFromToolbar && (block.type === 'table' || block.type.startsWith('table_v'))
)

expect(detail?.type).toBe('table')
expect(listed?.type).toBe('table')
})

it('resolves Slack to the released slack_v2 block', async () => {
const { getLatestBlockForViewer, getAllBlocks } = await import('@/blocks/registry')
const detail = getLatestBlockForViewer('slack')
const listed = getAllBlocks().find(
(block) =>
!block.hideFromToolbar && (block.type === 'slack' || block.type.startsWith('slack_v'))
)

expect(detail?.type).toBe('slack_v2')
expect(listed?.type).toBe('slack_v2')
})
})
9 changes: 4 additions & 5 deletions apps/sim/blocks/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,10 @@ export function getLatestBlockForViewer(type: string): BlockConfig | undefined {
*
* The detail read walks these rather than taking `getLatestBlock` and hiding
* the result, because "newest" and "visible to this viewer" are different
* questions. `slack_v2` is `preview`-gated while `slack` v1 deliberately stays
* in the toolbar so the workspace has a Slack block at all — so resolving to
* the newest and then hiding it answers `404` for a type the list is
* simultaneously publishing. Walking down to the newest *visible* version is
* what makes the two agree.
* questions. For example, `table_v2` is `preview`-gated while `table` remains
* in the toolbar — so resolving to the newest and then hiding it answers `404`
* for a type the list is simultaneously publishing. Walking down to the newest
* *visible* version is what makes the two agree.
*/
function versionCandidates(type: string): BlockConfig[] {
const normalized = normalizeType(type)
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/copilot/integration-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ vi.mock('@/blocks/registry-maps', () => ({
type: 'svc',
tools: { access: ['svc_send_v2'] },
},
// Preview successor sharing the released block's tools (the slack/slack_v2
// paradigm) — both owners must remain available for projection.
// A preview successor sharing the released block's tools keeps both owners
// available for projection.
svc_v2: {
type: 'svc_v2',
preview: true,
Expand Down
Loading