chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
/**
* @vitest-environment node
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { mockNotify } = vi.hoisted(() => ({ mockNotify: vi.fn() }))
vi.mock('@/blocks/custom/client-overlay', () => ({
notifyBlockOverlayChanged: mockNotify,
}))
import type { BlockVisibilityState } from '@/lib/core/config/block-visibility'
// client-boundary-allow: vitest ignores the 'use client' directive; this node-env test exercises the module directly
import { hydrateBlockVisibility, resetBlockVisibilityForSwitch } from '@/blocks/visibility/client'
import { overlayVisibility, registerBlockCacheInvalidator } from '@/blocks/visibility/context'
function state(revealed: string[], disabled: string[] = []): BlockVisibilityState {
return {
revealed: new Set(revealed),
disabled: new Set(disabled),
previewTagged: new Set(revealed),
}
}
describe('hydrateBlockVisibility', () => {
beforeEach(() => vi.clearAllMocks())
// Must run FIRST: module state starts null and persists across tests.
it('treats an empty state while none is set as a no-op (null ≡ empty)', () => {
const invalidator = vi.fn()
const unregister = registerBlockCacheInvalidator(invalidator)
hydrateBlockVisibility(state([]))
expect(overlayVisibility()).toBeNull()
expect(invalidator).not.toHaveBeenCalled()
expect(mockNotify).not.toHaveBeenCalled()
unregister()
})
it('applies state, fires invalidators, and bumps the overlay version', () => {
const invalidator = vi.fn()
const unregister = registerBlockCacheInvalidator(invalidator)
hydrateBlockVisibility(state(['gmail_v2']))
expect(overlayVisibility()?.revealed.has('gmail_v2')).toBe(true)
expect(invalidator).toHaveBeenCalledTimes(1)
expect(mockNotify).toHaveBeenCalledTimes(1)
unregister()
})
it('drops reveals but carries kill switches over on workspace switch', () => {
const invalidator = vi.fn()
const unregister = registerBlockCacheInvalidator(invalidator)
// Reveal + kill-switch in workspace A, then switch (loader resets while the
// new projection loads). (notion_v3: distinct from prior tests' state —
// module state persists.)
hydrateBlockVisibility(state(['notion_v3'], ['slack']))
resetBlockVisibilityForSwitch()
expect(overlayVisibility()?.revealed.size).toBe(0)
expect(overlayVisibility()?.previewTagged.size).toBe(0)
expect(overlayVisibility()?.disabled).toEqual(new Set(['slack']))
expect(invalidator).toHaveBeenCalledTimes(2)
// Repeated resets are no-ops (deep-equal).
resetBlockVisibilityForSwitch()
expect(invalidator).toHaveBeenCalledTimes(2)
unregister()
})
it('no-ops on a deep-equal state (fresh objects, same content)', () => {
const invalidator = vi.fn()
const unregister = registerBlockCacheInvalidator(invalidator)
hydrateBlockVisibility(state(['gmail_v2'], ['slack']))
hydrateBlockVisibility(state(['gmail_v2'], ['slack']))
expect(invalidator).toHaveBeenCalledTimes(1)
expect(mockNotify).toHaveBeenCalledTimes(1)
hydrateBlockVisibility(state(['gmail_v2', 'notion_v3'], ['slack']))
expect(invalidator).toHaveBeenCalledTimes(2)
expect(mockNotify).toHaveBeenCalledTimes(2)
unregister()
})
})
+70
View File
@@ -0,0 +1,70 @@
'use client'
import type { BlockVisibilityState } from '@/lib/core/config/block-visibility'
import { notifyBlockOverlayChanged } from '@/blocks/custom/client-overlay'
import { invalidateBlockCaches, registerBlockVisibilityResolver } from '@/blocks/visibility/context'
/**
* Client-side visibility state, hydrated from `useBlockVisibility` by
* `BlockVisibilityLoader`. Registered at module load with `null` state so the
* very first render — including the SSR pass — is fail-closed for `preview`
* blocks; the post-mount fetch only ever reveals (benign pop-in) or applies a
* kill switch to an already-public block.
*/
let state: BlockVisibilityState | null = null
registerBlockVisibilityResolver({ current: () => state })
function setsEqual(a: Set<string>, b: Set<string>): boolean {
if (a.size !== b.size) return false
for (const value of a) if (!b.has(value)) return false
return true
}
function isEmptyState(vis: BlockVisibilityState): boolean {
return vis.revealed.size === 0 && vis.disabled.size === 0 && vis.previewTagged.size === 0
}
/**
* Replace the in-scope visibility state, reset registered module caches, and
* bump the shared block-overlay version so every subscribed consumer re-reads
* `getAllBlocks()`.
*
* No-ops when the change cannot alter the projection: an incoming state
* deep-equal to the current one (React Query refetches deliver
* fresh-but-identical objects on every poll — without this guard each poll
* would thundering-rebuild the toolbar, search, and matcher caches), or an
* empty state while none is set (`null` and empty are equivalent for
* `isHiddenUnder`, so the fail-closed reset on first mount is free).
*/
export function hydrateBlockVisibility(next: BlockVisibilityState): void {
if (state === null && isEmptyState(next)) return
if (
state &&
setsEqual(state.revealed, next.revealed) &&
setsEqual(state.disabled, next.disabled) &&
setsEqual(state.previewTagged, next.previewTagged)
) {
return
}
state = next
invalidateBlockCaches()
notifyBlockOverlayChanged()
}
/**
* Fail-closed reset while a workspace switch's visibility fetch is in flight:
* preview reveals are dropped immediately (they may not apply to the new
* workspace), but kill-switch entries are CARRIED OVER until the new
* projection arrives — dropping `disabled` would flash kill-switched blocks
* back into discovery for the flight window, while briefly over-hiding in the
* new workspace is benign in both directions.
*/
export function resetBlockVisibilityForSwitch(): void {
if (state === null) return
hydrateBlockVisibility({
revealed: new Set(),
disabled: state.disabled,
previewTagged: new Set(),
})
}
+73
View File
@@ -0,0 +1,73 @@
import type { BlockVisibilityState } from '@/lib/core/config/block-visibility'
import type { BlockConfig } from '@/blocks/types'
/**
* Resolver for the per-viewer block-visibility projection, mirroring the
* custom-block overlay seam (`@/blocks/custom/overlay`) but deliberately
* independent of it: visibility is a discovery concern with its own lifecycle,
* and a separate AsyncLocalStorage composes with `withCustomBlockOverlay`
* without `store.run` clobbering.
*
* Two environment-specific resolvers register here:
* - client: module state hydrated from `useBlockVisibility` (see `client.ts`)
* - server: an AsyncLocalStorage scoped per request (see `server-context.ts`)
*
* This module is isomorphic (no `'use client'`, no `node:` imports) so
* `@/blocks/registry` stays importable on both sides. When NO resolver state is
* active, `preview` blocks are still hidden ({@link isHiddenUnder} treats a null
* state as "nothing revealed") — fail-closed is the default everywhere,
* including SSR and server paths outside `withBlockVisibility`.
*/
export interface BlockVisibilityResolver {
current(): BlockVisibilityState | null
}
let resolver: BlockVisibilityResolver | null = null
/** Register (or clear with `null`) the active visibility resolver for this environment. */
export function registerBlockVisibilityResolver(next: BlockVisibilityResolver | null): void {
resolver = next
}
/** The visibility state in scope, or `null` when none (= nothing revealed, nothing disabled). */
export function overlayVisibility(): BlockVisibilityState | null {
return resolver?.current() ?? null
}
/**
* THE single hidden-predicate for block gating — every surface that hides
* blocks (registry projection, VFS stamp filter, exposed-integration-tools
* filter, `get_blocks_metadata`) calls this; never restate the rule inline.
*
* A block is hidden when it is an unrevealed `preview` block (fail-closed even
* with a null state) or when the kill switch (`disabled`) names it. Static
* `hideFromToolbar` is deliberately NOT part of this predicate — callers that
* need it check it separately.
*/
export function isHiddenUnder(
vis: BlockVisibilityState | null,
block: Pick<BlockConfig, 'type' | 'preview'>
): boolean {
if (block.preview && !vis?.revealed.has(block.type)) return true
if (vis?.disabled.has(block.type)) return true
return false
}
/**
* Registry of non-React module-cache resets (e.g. the tool-operations search
* index, the integration matcher) fired when the client visibility state
* changes. Lives here — not in the `'use client'` module — so plain `lib/`
* modules can register at load time on either side of the server boundary.
*/
const cacheInvalidators = new Set<() => void>()
/** Register a cache reset to run on visibility changes. Returns an unregister fn. */
export function registerBlockCacheInvalidator(fn: () => void): () => void {
cacheInvalidators.add(fn)
return () => cacheInvalidators.delete(fn)
}
/** Fire every registered cache reset (called by the client hydrate path). */
export function invalidateBlockCaches(): void {
for (const fn of cacheInvalidators) fn()
}
@@ -0,0 +1,60 @@
/**
* @vitest-environment node
*/
import { afterEach, describe, expect, it, vi } from 'vitest'
vi.unmock('@/blocks/registry')
const { synthRegistry } = vi.hoisted(() => ({
synthRegistry: {
slack: {
type: 'slack',
name: 'Slack',
description: '',
category: 'tools',
bgColor: '#000',
icon: () => null,
subBlocks: [],
tools: { access: [] },
inputs: {},
outputs: {},
},
},
}))
vi.mock('@/blocks/registry-maps', () => ({
BLOCK_REGISTRY: synthRegistry,
BLOCK_META_REGISTRY: {},
}))
import { getAllBlocks } from '@/blocks/registry'
import { registerBlockVisibilityResolver } from '@/blocks/visibility/context'
afterEach(() => registerBlockVisibilityResolver(null))
describe('registry fast path (no preview blocks registered)', () => {
it('returns raw references with no context', () => {
expect(getAllBlocks()[0]).toBe(synthRegistry.slack)
})
it('returns raw references when the active state has no kill-switch entries', () => {
const state = {
revealed: new Set(['whatever']),
disabled: new Set<string>(),
previewTagged: new Set(['whatever']),
}
registerBlockVisibilityResolver({ current: () => state })
expect(getAllBlocks()[0]).toBe(synthRegistry.slack)
})
it('still projects when a kill-switch entry applies', () => {
const state = {
revealed: new Set<string>(),
disabled: new Set(['slack']),
previewTagged: new Set<string>(),
}
registerBlockVisibilityResolver({ current: () => state })
expect(getAllBlocks()[0]?.hideFromToolbar).toBe(true)
expect(synthRegistry.slack).not.toHaveProperty('hideFromToolbar', true)
})
})
@@ -0,0 +1,25 @@
import { AsyncLocalStorage } from 'node:async_hooks'
import type { BlockVisibilityState } from '@/lib/core/config/block-visibility'
import { registerBlockVisibilityResolver } from '@/blocks/visibility/context'
/**
* Server-side visibility context: a per-request AsyncLocalStorage, independent
* of the custom-block overlay's ALS so `withBlockVisibility` and
* `withCustomBlockOverlay` nest in either order without clobbering each other.
*
* Only copilot/mothership discovery paths establish this scope. Execution entry
* points (execute route, trigger.dev tasks, schedules/webhooks) never do — so
* placed preview blocks always serialize and run, and `preview` blocks stay
* hidden on unscoped discovery reads purely via the static fail-closed default.
*/
const store = new AsyncLocalStorage<BlockVisibilityState>()
registerBlockVisibilityResolver({ current: () => store.getStore() ?? null })
/** Run `fn` with the given visibility state resolvable via the registry accessors. */
export function withBlockVisibility<T>(
vis: BlockVisibilityState,
fn: () => Promise<T>
): Promise<T> {
return store.run(vis, fn)
}
@@ -0,0 +1,149 @@
/**
* @vitest-environment node
*/
import { afterEach, describe, expect, it, vi } from 'vitest'
vi.unmock('@/blocks/registry')
const { synthRegistry } = vi.hoisted(() => {
const block = (type: string, extra: Record<string, unknown> = {}) => ({
type,
name: type.toUpperCase(),
description: '',
category: 'tools',
bgColor: '#000',
icon: () => null,
subBlocks: [],
tools: { access: [] },
inputs: {},
outputs: {},
...extra,
})
return {
synthRegistry: {
slack: block('slack'),
gmail_v2: block('gmail_v2', { preview: true }),
old_v1: block('old_v1', { hideFromToolbar: true }),
},
}
})
vi.mock('@/blocks/registry-maps', () => ({
BLOCK_REGISTRY: synthRegistry,
BLOCK_META_REGISTRY: {},
}))
import type { BlockVisibilityState } from '@/lib/core/config/block-visibility'
import { registerBlockOverlayResolver } from '@/blocks/custom/overlay'
import { getAllBlocks, getBlock, getCanonicalBlocksByCategory } from '@/blocks/registry'
import type { BlockConfig } from '@/blocks/types'
import { isHiddenUnder, registerBlockVisibilityResolver } from '@/blocks/visibility/context'
function vis(partial: Partial<BlockVisibilityState> = {}): BlockVisibilityState {
return {
revealed: new Set(),
disabled: new Set(),
previewTagged: new Set(),
...partial,
}
}
function withVisibility(state: BlockVisibilityState | null) {
registerBlockVisibilityResolver(state ? { current: () => state } : null)
}
const byType = (blocks: BlockConfig[], type: string) => blocks.find((b) => b.type === type)
afterEach(() => {
registerBlockVisibilityResolver(null)
registerBlockOverlayResolver(null)
})
describe('isHiddenUnder', () => {
it('hides unrevealed preview blocks even with a null state (fail-closed)', () => {
expect(isHiddenUnder(null, { type: 'gmail_v2', preview: true })).toBe(true)
expect(isHiddenUnder(null, { type: 'slack' })).toBe(false)
})
it('reveals preview blocks named in revealed', () => {
const state = vis({ revealed: new Set(['gmail_v2']) })
expect(isHiddenUnder(state, { type: 'gmail_v2', preview: true })).toBe(false)
})
it('hides kill-switched types only with an active state', () => {
const state = vis({ disabled: new Set(['slack']) })
expect(isHiddenUnder(state, { type: 'slack' })).toBe(true)
expect(isHiddenUnder(null, { type: 'slack' })).toBe(false)
})
})
describe('registry projection', () => {
it('hides preview blocks without any context: clone-not-remove', () => {
withVisibility(null)
const all = getAllBlocks()
const gmail = byType(all, 'gmail_v2')
expect(gmail).toBeDefined()
expect(gmail?.hideFromToolbar).toBe(true)
// the underlying registry entry is untouched
expect(getBlock('gmail_v2')?.hideFromToolbar).toBeUndefined()
// canonical (filtered) set excludes it
expect(byType(getCanonicalBlocksByCategory('tools'), 'gmail_v2')).toBeUndefined()
})
it('reveals a preview block with a " (Preview)" display suffix when tagged', () => {
withVisibility(vis({ revealed: new Set(['gmail_v2']), previewTagged: new Set(['gmail_v2']) }))
expect(byType(getAllBlocks(), 'gmail_v2')?.name).toBe('GMAIL_V2 (Preview)')
const canonical = byType(getCanonicalBlocksByCategory('tools'), 'gmail_v2')
expect(canonical?.name).toBe('GMAIL_V2 (Preview)')
expect(canonical?.hideFromToolbar).toBeUndefined()
})
it('reveals a config-GA preview block without a suffix', () => {
withVisibility(vis({ revealed: new Set(['gmail_v2']) }))
expect(byType(getAllBlocks(), 'gmail_v2')?.name).toBe('GMAIL_V2')
})
it('kill-switches a shipped block only when a context is active', () => {
withVisibility(vis({ disabled: new Set(['slack']) }))
expect(byType(getAllBlocks(), 'slack')?.hideFromToolbar).toBe(true)
expect(byType(getCanonicalBlocksByCategory('tools'), 'slack')).toBeUndefined()
withVisibility(null)
expect(byType(getAllBlocks(), 'slack')?.hideFromToolbar).toBeUndefined()
})
it('keeps getBlock pure regardless of visibility', () => {
withVisibility(
vis({
revealed: new Set(['gmail_v2']),
previewTagged: new Set(['gmail_v2']),
disabled: new Set(['slack']),
})
)
expect(getBlock('gmail_v2')?.name).toBe('GMAIL_V2')
expect(getBlock('slack')?.hideFromToolbar).toBeUndefined()
})
it('returns untouched references for unaffected blocks', () => {
withVisibility(vis({ revealed: new Set(['gmail_v2']) }))
expect(byType(getAllBlocks(), 'slack')).toBe(synthRegistry.slack)
expect(byType(getAllBlocks(), 'old_v1')).toBe(synthRegistry.old_v1)
})
it('never re-clones or suffixes already-hidden custom blocks', () => {
const disabledCustom = {
...synthRegistry.slack,
type: 'custom_block_abc',
name: 'My Custom',
hideFromToolbar: true,
} as BlockConfig
registerBlockOverlayResolver({
get: (t) => (t === disabledCustom.type ? disabledCustom : undefined),
all: () => [disabledCustom],
})
withVisibility(vis({ revealed: new Set(['gmail_v2']) }))
const projected = byType(getAllBlocks(), 'custom_block_abc')
expect(projected).toBe(disabledCustom)
expect(projected?.name).toBe('My Custom')
})
})