fixed intellisense and autocomplete for connect pipeline editor and fixed CSS cascade issue with popover not showing#2272
Conversation
…ixed CSS cascade issue with popover not showing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@claude please review |
|
Claude finished @eblairmckee's task in 1m 44s —— View job Code Review
Overall, this is a well-researched, well-documented PR with targeted, minimal fixes. The root cause analysis is thorough. A few things worth discussing:
|
| Area | Finding | Severity |
|---|---|---|
yaml-editor.tsx |
hasInitializedRef logic correct but add a comment |
Low |
yaml-editor.tsx |
Static path prop shared across instances |
Low (noted as open risk) |
yaml-editor.tsx |
schema reference instability → spurious update() |
Low (pre-existing) |
config.ts |
Add comment explaining omitted loader.init() |
Low |
globals.css |
CSS override is correct ✅ | — |
These are all minor. The core fixes (CSS override, path prop, beforeMount timing, fixedOverflowWidgets, update() API, synchronous MonacoEnvironment, URI normalization) are all sound and well-justified. LGTM with the minor suggestions above.
malinskibeniamin
left a comment
There was a problem hiding this comment.
Now that it is working, it would be great to add some type of test (I guess in this case visual test would be best) to make sure monaco editor with schema provided will give you good autocomplete suggestions.
|
@claude could you review and double check removing |
|
Claude finished @sago2k8's task in 1m 21s —— View job Review: Is removing
|
| Old code | New code |
|---|---|
Schema update: dispose() + configureMonacoYaml() |
Schema update: update() |
Unmount: dispose() ✅ |
Unmount: dispose() ✅ |
The dispose() removal from the schema update path is strictly better — it uses the purpose-built update() API, avoids a timing gap, and keeps exactly one MonacoYaml instance alive at all times. No breaking change.
| // Set MonacoEnvironment synchronously before loader.init() to avoid race | ||
| // where the editor mounts before the worker URL resolver is available | ||
| window.MonacoEnvironment = { | ||
| getWorkerUrl(_, label: string): string { | ||
| switch (label) { | ||
| case 'editorWorkerService': { | ||
| return `${window.location.origin}/static/js/editor.worker.js`; | ||
| } | ||
| case 'typescript': { | ||
| return `${window.location.origin}/static/js/ts.worker.js`; | ||
| } | ||
| default: { | ||
| return `${window.location.origin}/static/js/${label}.worker.js`; | ||
| } | ||
| } | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Ideally we could try and add a test that verifies these files exist in the build/bundle.
There was a problem hiding this comment.
Good call — I looked into this. The challenge is that vitest runs in node/jsdom where workers don't exist and there's no build output to inspect. The most practical option would be a post-build CI step that asserts worker files exist in build/static/js/. However, that requires building first and would need a new test:build script + CI step. For now, the MonacoWebpackPlugin config is the source of truth for what gets emitted — if the filename pattern or languages list changes there, the workers change. Adding to our backlog to add a post-build check for this, or defer until we decide on a replacement for monaco.
There was a problem hiding this comment.
Adding this requirement to the existing ticket https://redpandadata.atlassian.net/browse/UX-589
| /* tw-animate-css defines .fade-in { opacity: 0; animation: .3s forwards fadeIn; } | ||
| which collides with Monaco's internal .monaco-hover.fade-in class, | ||
| forcing hover widgets to be invisible. Override to restore opacity. */ | ||
| .monaco-hover.fade-in { | ||
| opacity: 1; | ||
| } |
There was a problem hiding this comment.
do we need to add it to the host app too?
There was a problem hiding this comment.
like cloudv2? no clue. depends on what order the css stylesheets are loaded.
There was a problem hiding this comment.
No worries, just double checking we don't get any CSS interference in embedded mode.
unfortunately they're not "good" right now as they are literally just key autocompletions. until we get the bloblang/connect schema aware LSP (WIP by connect team) it's literally just autocomplete for keys in the rp-connect-schema.json file I did consider adding an intergration test, but hesitated due to web workers + vitest potentially being flakey (but tbh, I didn't look into it yet). |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Fix Monaco YAML Editor Intellisense & Popovers
Problem
The Monaco YAML editor has two distinct failures:
PipelinesYamlEditor) — hover tooltips show a border but content is invisible. Autocomplete popovers work fine.YamlEditoronly) — the JSON schema passed toconfigureMonacoYamlproduces no completions or validation in the pipeline page.The intellisense issue is specific to the new
YamlEditorrefactor. The hover popover issue is global and caused by a Tailwind v4 CSS class name collision.Assumptions
Success criteria: When editing YAML in the pipeline page, (1) hover tooltips and autocomplete suggestions are visible with correct styling, and (2) typing a top-level key triggers schema-driven completions showing valid Redpanda Connect component names.
Constraints & Non-Goals
Constraints:
YamlEditorcomponent (not revert to old one)Non-goals:
PipelinesYamlEditorRoot Cause Analysis
Root Cause 1: Missing
pathprop (kills intellisense)The old editor sets
path={'new-connector.yaml'}. The new editor sets nopath. Monaco-yaml matches schemas to editor models viafileMatch: ['**/*.yaml']. Without a.yamlpath, the model gets an auto-generated URI likeinmemory://model/1that doesn't match the glob, so the schema is never applied.Fix: Add
path="pipeline.yaml"(or any*.yamlname) to the<Editor>component inyaml-editor.tsx.Root Cause 2:
onMountvsbeforeMounttiming (may cause race)The old editor calls
configureMonacoYamlinbeforeMount. The new editor calls it inonMount. When called inonMount, the editor model already exists. Monaco-yaml applies schemas to models at configuration time — if the model exists before the schema is configured, it may not get the schema applied.Fix: Switch from
onMounttobeforeMountfor the initialconfigureMonacoYamlcall, matching the working old editor pattern.Root Cause 3:
overflow-hiddenon parent container (clips widgets)yaml-editor-card.tsx:88-89wraps the editor in a div withoverflow-hidden. Monaco's hover tooltips and autocomplete widgets are absolutely positioned and extend beyond the editor bounds.overflow-hiddenclips them, making them invisible.Fix: Add
fixedOverflowWidgets: trueto the editor's default options. This tells Monaco to render widgets usingposition: fixed(or into an overflow widget container), bypassing the parent'soverflow-hidden.Root Cause 4:
tw-animate-css.fade-inclass collision with Monaco [CONFIDENCE: HIGH — confirmed via DevTools]tw-animate-css(imported inglobals.css) defines.fade-in { opacity: 0; animation: .3s forwards fadeIn; }. Monaco internally uses afade-inclass on.monaco-hoverfor its own hover animation (.monaco-hover.fade-in { animation: .1s linear fadein; }). Monaco's rule doesn't setopacity, so thetw-animate-cssrule'sopacity: 0applies and is never overridden, making hover content permanently invisible.This explains why:
monaco-resizable-hovercontainer doesn't use.fade-in)opacity: 0fromtw-animate-css)fade-inclass)PipelinesYamlEditorFix: Add a CSS override in
globals.cssafter thetw-animate-cssimport:Root Cause 5: Double
configureMonacoYamlcall on mountIn
yaml-editor.tsx,configureMonacoYamlis called inonMount(line 132) AND theuseEffectonmonacoYamlOptions(line 115) fires immediately after becausemonacoRef.currentwas just set. This means it's disposed and re-created back-to-back. The monaco-yaml docs state: "There may only be one configured instance of monaco-yaml at a time." The double-call with dispose in between may cause a race condition where the schema isn't applied to the already-created model.Fix: Only call
configureMonacoYamlonce — inbeforeMountfor initial setup. TheuseEffectshould only handle subsequent schema changes (guard with a "has mounted" check).Root Cause 6:
window.MonacoEnvironmentset asynchronously [CONFIDENCE: MEDIUM]In
config.ts:398-414,window.MonacoEnvironment(which providesgetWorkerUrl) is set insideloader.init().then(...). Even thoughloader.config({ monaco })makesloader.init()resolve immediately, the.then()callback executes as a microtask — meaningMonacoEnvironmentmay not be set when the editor first tries to spawn the YAML worker. If the worker doesn't load, no completions, hover, or validation will work.Fix: Move
window.MonacoEnvironment = { getWorkerUrl(...) {...} }before theloader.init()call so it's set synchronously.Proposed Fix
No architecture changes needed. The fixes are targeted:
globals.css.monaco-hover.fade-in { opacity: 1; }to fixtw-animate-csscollisionyaml-editor.tsxpath="pipeline.yaml"to<Editor>yaml-editor.tsxconfigureMonacoYamlfromonMounttobeforeMountyaml-editor.tsxfixedOverflowWidgets: truetodefaultOptionsoverflow-hiddenyaml-editor.tsxupdate()API instead of dispose+recreate; skip initialuseEffectwithhasInitializedRefconfig.tswindow.MonacoEnvironmentbeforeloader.init()yaml-editor.tsx{ type: 'object' }) to prevent URI fetchyaml-editor.tsxhttps://redpanda-connect-schema.jsontohttps://redpanda.com/connect-schema.jsonRoot Cause 7: URI normalization mismatch in json-language-service (THE ACTUAL BUG)
Discovered after initial fixes. Error:
Unable to load schema from 'https://redpanda-connect-schema.json'. No schema request service available YAML(768)Root cause:
vscode-urinormalizeshttps://redpanda-connect-schema.json→https://redpanda-connect-schema.json/(adds trailing/because the parsed path component becomes/). Thejson-language-servicehas a bug inregisterExternalSchema(yaml.worker.js:1421): it stores the schema handle under the normalized URI but stores the file pattern association with the original URI. When validation looks up the schema, it retrieves the original URI from the file pattern, passes it togetOrAddSchemaHandle, which looks upschemasById[originalUri]— but the entry is underschemasById[normalizedUri]. The lookup fails, creating a new empty SchemaHandle that triggersloadSchema()→ no request service → error.Confirmed via
vscode-uri:The fix: Change the schema URI to one with a proper path component that normalizes to itself:
https://redpanda.com/connect-schema.json.Root Cause 8: Dispose+recreate lifecycle (secondary issue, also fixed)
The old code disposed
configureMonacoYamland recreated it on schema changes. TheconfigureMonacoYamlAPI provides anupdate()method specifically for this purpose — it properly restarts the YAML worker with new schema data without tearing down and re-registering all language providers.Additionally, always provide an inline schema object (even the empty
{ type: 'object' }fallback) so theSchemaHandlealways hasunresolvedSchemaset.Tradeoffs
fixedOverflowWidgets: true— Widgets render outside the editor container, which is correct for our layout. Potential downside: if the editor is inside a modal/dialog, widgets may appear outside the dialog. This is already handled by the expanded-yaml-dialog using its own container.beforeMountinstead ofonMount— We lose access to the editor instance at configuration time, butconfigureMonacoYamlonly needs themonaconamespace, not the editor instance. TheuseEffectfor schema updates still works viayamlRef.current.update().pathprop — AllYamlEditorinstances will share the same model URI path. If multiple editors exist simultaneously, they'd share the schema config. This is fine sinceconfigureMonacoYamlis global (one instance at a time per monaco-yaml docs).update()vs dispose+recreate —update()is the intended API for schema changes. It avoids re-registering all language providers and avoids the timing gap that caused the schema request error. Downside: none — this is strictly better.Reviewer Critique
.hiddencollision) is HIGH confidence — confirmed via DOM inspection showing.monaco-hover.hiddenwith content present but invisible. Root Cause 6 (MonacoEnvironment race) is MEDIUM — needs verificationoverflowWidgetsDomNodeinstead offixedOverflowWidgets, but that requires creating a DOM ref — more complexity for same outcome. Could use Tailwind prefix to avoid class collisions globally, but that's a massive migrationImplementation Steps
Step 1: Fix
tw-animate-css.fade-incollision (highest impact — fixes all editors) ✅Added CSS override to
globals.cssafter thetw-animate-cssimport:Step 2: Fix
pathprop ✅Added
path="pipeline.yaml"to<Editor>inyaml-editor.tsx.Step 3: Fix timing ✅
Moved
configureMonacoYamlfromonMounttobeforeMount.Step 4: Fix widget clipping ✅
Added
fixedOverflowWidgets: truetodefaultOptionsinyaml-editor.tsx.Step 5: Fix schema update lifecycle (revised from original plan) ✅
Original plan: guard
useEffectwithhasMountedRef. Problem:hasMountedRefwas set inbeforeMountwhich runs before effects, so the guard never prevented the initial effect from firing.Revised approach:
hasInitializedRef(startsfalse) — the initial effect sets it totrueand returns earlyyamlRef.current.update(monacoYamlOptions)instead of dispose+recreate{ type: 'object' }) soSchemaHandlenever falls back toloadSchema()buildMonacoYamlOptions()helper handles both casesStep 6: Fix MonacoEnvironment race ✅
Moved
window.MonacoEnvironment = { getWorkerUrl(...) {...} }beforeloader.init()inconfig.ts.Step 7: Verify
Test on both old and new editors:
Editor Comparison: Old (
PipelinesYamlEditor) vs New (YamlEditor)PipelinesYamlEditor)YamlEditor+YamlEditorCard)rp-connect-schema.json)beforeMountonMount+ reactiveuseEffectpathproppath="new-connector.yaml"fileMatch)kowlEditorclassoverflow-hiddenExpandedYamlDialogUsage Locations
Old
PipelinesYamlEditor:pipelines-create.tsx— CREATE mode (with secrets autocomplete)pipelines-details.tsx— VIEW mode (read-only, 400px height)redpanda-connect-intro.tsx— interactive demoNew
YamlEditorCard:pipeline/index.tsx— CREATE/EDIT/VIEW (dynamic schema from backend)mcp-servers/create/tool-card.tsx— EDIT (no schema passed)mcp-servers/details/remote-mcp-configuration-tab.tsx— EDIT/VIEW (no schema passed)expanded-yaml-dialog.tsx— expanded dialog (no schema passed)Tradeoffs
pathprop andonMounttiming. Once fixed, it's strictly better for new development.CSS Collision Audit:
tw-animate-css& Tailwind Preflight vs Monacotw-animate-cssCollisionsfade-intw-animate-csssets opacity:0; breaks Monaco hover widgetshiddendisplay: none— behavior matches, no visible buginvisiblevisibility: hidden, Monaco:opacity: 0) but Monaco scopes with child combinatortw-animate-cssand MonacoTailwind v4 Preflight Impacts
h1-h6 { font-size/weight: inherit }line-heightbut relies on browser defaults for size/weightimg, svg { display: block }ol, ul { list-style: none }a { color: inherit }button/input resets* { border: 0 solid }Action: The preflight issues are cosmetic and only affect rich Markdown content in hover tooltips (JSDoc, etc.). Not blocking for YAML editor use case. Can be addressed later with scoped CSS if needed.
Key Files
src/components/ui/yaml/yaml-editor.tsxsrc/components/ui/yaml/yaml-editor-card.tsxoverflow-hiddensrc/components/misc/pipelines-yaml-editor.tsxsrc/config.tsMonacoEnvironmentsetuprsbuild.config.tscustomLanguagessrc/components/redpanda-ui/style/theme.cssOpen Risks
yaml.worker.jsisn't loading, intellisense fixes won't help — verify in DevTools Network tabKnown Unknowns
monaco-editorinnode_modules? → Verify withbun pm ls monaco-editorReferences