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
…mizer (#6117) * perf(tests): mock the tool registry globally, drop the dead deps optimizer The test suite spent far more time importing modules than running them: on the full suite, `import` was 1,399s aggregate against 88s of actual tests. Per-file import cost showed exactly where it came from — lib/core, which touches no registry, runs at 0.09s/file, while every area that reaches the tool registry runs 12x-79x that (blocks 7.12s/file, providers 3.65, executor 3.07, tools 2.05, app/api 1.05). The tool registry is 4,351 entries pulling ~5,907 modules, and almost nothing under test needs the real thing. `@/blocks/registry` was already globally mocked for this reason; this does the same for `@/tools/registry`. Full suite, same commit, same machine: baseline Duration 166.21s (transform 141.80s, import 1399.17s) after Duration 91.56s (transform 61.98s, import 617.08s) 45% faster, import -56%, transform -56%. Identical results either way: 1252 files / 16873 tests pass, plus one failure that reproduces on unmodified staging (cloud-review-tools.test.ts cannot find `rg` from its spawned python3 locally; CI installs ripgrep and it passes there). Four test files genuinely assert tool registration or tool params, so they opt out with `vi.unmock('@/tools/registry')` rather than being weakened or deleted — outlook, azure_devops, and the two search-replace suites. No coverage is lost. Also removes `deps.optimizer.web`, which was dead config: it only applies to client environments (jsdom/happy-dom) and 985 of 1,219 files declare `@vitest-environment node`. Measured both ways to be sure — removing it is a no-op (19.33s -> 19.23s), and switching it to the correct `ssr` side with an include list for the heavy provider SDKs was also a no-op (19.19s). The cost is first-party module graph, which the optimizer does not touch, so the honest move is to delete it rather than leave config that reads as if it does something. Adds the missing `getBlockRegistry` accessor to the existing `@/blocks/registry` mock. #6083 renamed that export and the mock was never updated, so any test reaching those three consumers would have hit "getBlockRegistry is not a function". Nothing exercises them today. Not done, deliberately: `isolate: false` is ~20% faster but leaks state between files and broke two doc-servable tests on the first run. * docs(tests): explain why the search-replace registry opt-outs are load-bearing Review read the missing direct import of @/tools/registry as evidence the vi.unmock calls were no-ops. They are not: the dependency is transitive — the search-replace planner resolves tool input params through real subblock configs — and removing both opt-outs fails 8 tests across the two suites. Comment now says that, so the next reader does not delete them.
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