fix(blocks): stop registry.ts reading BLOCK_REGISTRY at module scope - #6083
Conversation
`blocks/registry-maps` and `blocks/registry` sit in an import cycle: a block
config reaches back through providers/utils -> tools/params -> blocks/index
-> blocks/registry, which imports registry-maps. Whichever module the cycle
is entered through evaluates first, so `blocks/registry` can run while
`registry-maps` is still initializing.
Two module-scope reads of the imported binding therefore threw
`ReferenceError: Cannot access 'BLOCK_REGISTRY' before initialization` for
anything importing `@/blocks/registry-maps` directly — scripts and tests
under Bun:
const HAS_PREVIEW_BLOCKS = Object.values(BLOCK_REGISTRY).some(...) // line 28
export const registry: Record<string, BlockConfig> = BLOCK_REGISTRY // line 238
Only line 28 showed in the stack trace; fixing it alone would have moved the
failure to line 238. Both are now deferred: `anyPreviewBlocks()` memoizes on
first use (still computed once, as the old comment promised), and the raw map
is exposed as `getBlockRegistry()`.
This worked under Turbopack only because that bundler happened to order the
modules favourably — not a property to build on, and any registry
restructuring would perturb it. Breaking it now is a prerequisite for the
metadata/implementation split that the tool-registry module-graph audit
proposes, not a fix to discover midway through one.
Three consumers updated. `tsc` found two that grep missed, because they
destructure the binding off a dynamic import (`const { registry: blockRegistry }
= await import(...)`) rather than naming it in a static import.
Behaviour-preserving: `getBlockRegistry()[type]` is the same raw lookup the
alias gave, deliberately not `getBlock()`, which would add version
normalization and custom-block overlay fallback.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview
Call sites that needed the raw map now call Reviewed by Cursor Bugbot for commit b34b2d1. Configure here. |
Greptile SummaryDefers module-scope
Confidence Score: 5/5Safe to merge; the cycle fix is localized, callers of the removed export are updated, and no behavioral or security regressions are evident. Both module-scope BLOCK_REGISTRY reads are deferred; remaining production imports use getBlock/getAllBlocks or the new getter; no leftover named registry import was found.
|
| Filename | Overview |
|---|---|
| apps/sim/blocks/registry.ts | Lazily reads BLOCK_REGISTRY for preview detection and raw-map export to avoid TDZ under the registry/registry-maps cycle. |
| apps/sim/app/workspace/[workspaceId]/home/components/chat-context-kind-registry/chat-context-kind-registry.tsx | Switches static import from registry alias to getBlockRegistry() with the same raw type lookup. |
| apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-mention-data.ts | Dynamic import updated to call getBlockRegistry() instead of destructuring removed registry binding. |
| apps/sim/lib/copilot/chat/process-contents.ts | Dynamic import updated to getBlockRegistry() for blockId existence check; behavior unchanged vs prior alias. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Entry: import registry-maps or registry] --> B{Import cycle}
B --> C[registry-maps still initializing]
B --> D[registry evaluating]
C --> E[Old: module-scope BLOCK_REGISTRY read]
E --> F[ReferenceError TDZ]
D --> G[New: anyPreviewBlocks / getBlockRegistry on first use]
G --> H[BLOCK_REGISTRY fully initialized]
H --> I[Safe lookup]
Reviews (1): Last reviewed commit: "fix(blocks): stop registry.ts reading BL..." | Re-trigger Greptile
Summary
import('@/blocks/registry-maps')throws today:blocks/registry-mapsandblocks/registrysit in an import cycle — a block config reaches back throughproviders/utils→tools/params→blocks/index→blocks/registry, which importsregistry-maps. Whichever module the cycle is entered through evaluates first, soblocks/registrycan run whileregistry-mapsis still initializing.Two module-scope reads, not one
Only line 28 appears in the stack trace, so fixing it alone would have silently moved the failure to line 238. Both are now deferred:
anyPreviewBlocks()memoizes on first use — still "computed once", as the old comment promised, just not at import timegetBlockRegistry()instead of an eagerconstaliasScope: only the entry point was broken
Worth being precise, because the original report overstated it.
@/blocks/registryand@/tools/registryboth imported fine before this change; only@/blocks/registry-mapsas the entry point threw. After:@/blocks/registry-maps@/blocks/registry@/tools/registry@/blocks/index,@/tools/params,@/tools/utils,@/providers/utilsWhy fix it if the app works
It works under Turbopack only because that bundler happens to order the modules favourably. That's not a property to build on, and it makes
blocks/registry-mapsun-importable from scripts and tests under Bun.More concretely: the tool-registry module-graph audit found the registry is 68–78% of every workspace route's graph and proposed a metadata/implementation split. Any such restructuring perturbs module ordering. This is a prerequisite to that work, not something to discover halfway through it.
tsccaught two consumers grep missedThree consumers updated. Two of them destructure the binding off a dynamic import rather than naming it in a static one:
My
rgfor the named import found only one callsite;tsc --noEmitfound the other two (use-mention-data.ts:195,process-contents.ts:548). Worth noting for anyone doing similar refactors — a grep-only sweep here would have shipped two runtimeundefinedlookups.Behaviour preservation
getBlockRegistry()[type]is the same raw lookup the alias provided. Deliberately notgetBlock(), which would additionally apply version normalization and custom-block overlay fallback — correct-looking, but a behaviour change smuggled into a cycle fix.Type of Change
Testing
tsc --noEmitexit 0apps/sim/blocksandapps/sim/lib/copilot/chatbiome checkclean on all changed files