RI-8326 Collapse large vector embeddings in the query editor#6267
Conversation
Add getEmbeddingKey to derive a stable identity for a detected vector embedding, preferring the PARAMS argument name and falling back to a content signature. This lets the upcoming collapse toggle remember each embedding's hide/show state across query edits, where character offsets shift and cannot be used as keys. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a session-scoped store that backs collapsed vector embeddings:
collapsing swaps the embedding text for a short id-carrying placeholder
("[▸ vector · 1536 dims #1]") and keeps the full value in memory;
expandVectorEmbeddings puts the values back into any text before it
leaves the editor (submit, copy, save, expand). Unknown ids are left
untouched so a stale placeholder stays visible instead of failing
silently.
Part of RI-8326.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Monaco hook that collapses each detected embedding by replacing
its text with a short placeholder in the model and expands it back on
click. The model really contains the placeholder — so cursor movement,
wrapping and navigation behave as if the blob were gone — while the full
value lives in the placeholder store.
All detection and slicing runs off the live model text, not the React
`query` state (which lags the model by a render), so an in-flight
collapse or expand never bails on stale offsets and the collapse chip
never disappears; `query` is only a change trigger. The placeholder text
is hidden entirely and the collapsed chip is drawn as a single injected
span ("▸ vector · N dims") — rendering it from the model text would let
the Redis tokenizer syntax-colour it and split the chip background per
token. The expanded-state collapse arrow ("▾") is injected over the
first character of the blob (not a zero-width range, which Monaco won't
render injected text on). Full values are restored on copy (clipboard
listener); submit/save expand via the store's expandVectorEmbeddings.
Embeddings auto-collapse once per content key (getEmbeddingKey) so undo
and an explicit expand are not fought by the next render. Both detected
formats (binary \x blobs and numeric arrays) are covered. Adds the en/bg
chip label.
Part of RI-8326.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire useVectorEmbeddingCollapse into the shared VectorEmbeddingHighlight component so detected embeddings collapse into a clickable chip in both the Workbench and Vector Search editors. Expand collapsed placeholders back to the full values at every exit path: on submit (query editor context) and when saving a query to the library (vector search wrapper); copy is handled inside the hook. Style the chip as an inlay-hint-like rounded label and hide the underlying placeholder text. Closes RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Track only the embeddings the user explicitly expanded instead of the ones already auto-collapsed once. Re-pasting an embedding whose content key was seen before (e.g. the same PARAMS name) now collapses again rather than showing the full blob, while an expanded embedding stays open until the user re-collapses it. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pasting a query scrolls Monaco to the end of the long blob; once the blob is collapsed there is nothing to scroll to and the caret is left at the end. Reset the caret and viewport to the top right after the auto-collapse edit so the user sees the start of their query. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inject a copy icon to the right of a collapsed embedding chip; clicking it copies the full embedding value to the clipboard via handleCopy without expanding the chip. Also folds in the chip styling tweaks (notice tint, sizing). Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the copy button share one pill with the toggle label instead of sitting beside it: the label styles as the left half and the copy button as the right half of a single chip (matching background, split corner radii), while staying separate elements so each keeps its own click action. The expanded state gets its own class for a standalone, fully-rounded collapse arrow. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep the toggle label as its own chip and render the copy button as a separate pill next to it rather than fused into one pill. Add a native "Copy" tooltip on the copy button, set lazily on hover (Monaco re-renders the injected span, so a one-off set would be lost). Adds the en/bg copy string. The reusable CopyButton React component can't be used inline here — Monaco inline chips must be injected text, and a floating content widget would not reserve inline space — so the copy affordance stays an injected element; CopyButton fits the planned preview popup instead. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the injected copy glyph with the design-system CopyButton. A React component can't be injected inline, so the collapse hook manages a Monaco content widget that floats above the hovered chip and exposes its DOM node; VectorEmbeddingHighlight portals CopyButton into it. The button brings its own tooltip and "Copied" badge and copies the full embedding. The widget shows on chip hover and stays while the pointer is over it (a short grace delay bridges the gap), so the query text is never disturbed. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hover copy widget never showed: it was triggered from a raw DOM mouseover via getTargetAtClientPoint, which returns no model position for injected-text spans, and it laid out the content widget before the CopyButton portal had rendered into it (so Monaco measured an empty node). Drive the hover from Monaco's onMouseMove instead (same target resolution as onMouseDown, which already resolves the chip), dedupe by placeholder id to avoid state churn, and re-layout the widget in an effect after copyValue changes so it is positioned against the real button size. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…chip Replace the fragile hover content widget (which never reliably appeared) with a simple injected copy button rendered right after the toggle label whenever an embedding is collapsed. Clicking it copies the full value via handleCopy without expanding; a native "Copy" tooltip is set on hover. A real React component can't be injected inline in Monaco, so this is an injected element styled as a button rather than the CopyButton component — reliable and always visible, which is what the inline case needs. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Monaco renders injected text with pointer-events: none, which suppressed the pointer cursor, the :hover state and the DOM mouseover that sets the copy button's tooltip. Set pointer-events: auto on the chips so the copy button behaves like a button — pointer cursor, hover highlight and the "Copy" tooltip on hover. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the hover background to the toggle label and expanded arrow, not just the copy button. Fix the copy tooltip: setting title during mouseover only surfaces on the next hover, and Monaco re-creates the injected span on every render, so stamp the "Copy" title as soon as each copy span appears via a MutationObserver instead. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Give the collapsed chip the same Monaco hoverMessage the expanded embedding uses (query.editor.vectorEmbedding.hover — dimensions and byte size), replacing the unreliable native title tooltip. The message sits on the placeholder's real (hidden) text, so the hover triggers reliably and instantly, styled like the rest of the editor. The placeholder store now keeps each embedding's byte size so the hover can render it while collapsed. Drops the now-unused copy tooltip string. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Give the copy button on the right of the collapsed chip a simple native "Copy" tooltip. It is stamped onto the injected span as soon as it renders (via a MutationObserver) so it is present before the pointer arrives — setting it on hover only surfaced on the next hover. The chip keeps its metadata hover; the copy button adds the Copy hint. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The copy button lived on the same decoration as the metadata hoverMessage, so hovering it showed the metadata instead of a copy hint. Split the collapsed chip into separate decorations: the label carries the metadata hover, the copy button carries its own "Copy" Monaco hover. Both use hoverMessage (reliable) instead of the native title, which never surfaced. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking the copy button placed Monaco's caret at the click point, which is the zero-width placeholder — so the caret appeared on top of the chip. Capture the caret position before Monaco moves it (capture-phase mousedown) and restore it right after copying, so copying leaves the caret where it was. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the caret restore from the copy button to the expand/collapse toggle, so clicking the collapsed chip (or the expand arrow) no longer drops Monaco's caret onto the zero-width chip. The pre-click position, already captured for all chip clicks, is restored after the edit. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Handle chip clicks (copy, expand, collapse) in a capture-phase mousedown that stops the event before Monaco processes it, so Monaco never moves the caret onto the zero-width chip — not even during a click-and-hold. The actions run in the handler; expand/collapse restore the prior selection around the edit. Replaces the previous editor.onMouseDown + post-hoc caret restore, which still flashed the caret while held. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Style the toggle label, expand arrow and copy button like the selected "Query editor" tab — primary brand background (primary400) with light text (primary50) and a primary hover shade. Uses semantic tokens so it adapts to the light and dark themes automatically. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch the chip text from text.primary50 to text.neutral100, which resolves to near-black (#121212) in dark mode and white (#ffffff) in light mode — black text in dark theme, light text in light theme — so the label and copy button read clearly on the primary chip in both. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Monaco ignores the CSS colour on injected text, so the chip label keeps the editor's default foreground — light in dark theme, dark in light theme. Use background.primary200, which is a dark-blue chip in dark mode and a light-blue chip in light mode, so that default text stays readable in both. Drops the ignored colour override. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove narration/redundant comments across the collapse hook, styles and placeholder store, keeping only the non-obvious Monaco-quirk notes. Also drop the unused VectorEmbeddingPlaceholder.visibleRange and hasValue fields (never read in production) and the constant that fed them; update the placeholder spec accordingly. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the chip class names, glyphs and edit source to a shared constants module (used by both the hook and the styles, so the CSS class contract lives in one place), and extract toMonacoRange plus a generic findAtOffset helper — replacing the duplicated findPlaceholderAt/findMarkAt. Strip the remaining narration comments across the hook, styles and placeholder store, keeping only the few non-obvious Monaco-quirk notes. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After a successful copy, the copy button briefly shows a ✓ (for ~1.5s) before reverting to the copy icon, confirming the value was copied. Tracked by a copiedId state that re-renders the chip decoration. Part of RI-8326. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3babd7c46
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
resetVectorEmbeddingPlaceholders hardcoded a predictable 'sess' prefix, which would downgrade placeholder ids to a guessable, fixed value if the exported helper were ever called in production. Regenerate a CSPRNG-based id, matching module init, so the unguessable-prefix property always holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e196eaf13
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The model placeholder contained spaces, so the FT.SEARCH arg tokenizer split it into several tokens. With multiple PARAMS embeddings, collapsing an earlier value shifted the name/value pairing and later embeddings lost their PARAMS name — falling back to the content key, which re-collapsed them mid-edit. Make the placeholder space-free so it parses as one argument; the visible chip label (with spaces) is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ff5cb4db2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Auto-collapse fired on the raw embedding text that Monaco restores on undo/redo, so Ctrl+Z could never step back past a collapse (it bounced straight to the placeholder). Track the editor's isUndoing/isRedoing content changes and skip the auto-collapse pass once when the change came from undo/redo; a later genuine edit still collapses as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ba5e38d753
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The one-shot skip flag suppressed only the first effect pass after an undo, so an unrelated later re-render (e.g. the copied-tick timer) still re-collapsed the restored embedding. Instead mark undo/redo-restored embeddings as user-expanded, so they stay put until removed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 67fb851b4f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
executeEdits adds an undo entry but no undo stop, so the auto-collapse could merge with the paste/type that produced the vector — Ctrl+Z then skipped the raw-vector state and the isUndoing guard never ran. Wrap the collapse edit in explicit undo stops so undo lands on the raw value first. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e4be1eb. Configure here.
The auto-collapse unconditionally moved the cursor to line 1, which yanked the caret away when a collapse fired mid-edit (e.g. finishing a large array). Preserve and restore the selection across the edit, like the manual expand/collapse handlers, so the caret stays where the user is. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e73f97237
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The copy/cut interception read only editor.getSelection(), so with multiple cursors the secondary ranges were dropped from the clipboard or kept raw placeholder tokens. Handle all selections via getSelections(), ordered top-to-bottom, expanding each range (and deleting each on cut). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the one-time editor listener/subscription wiring out of the per-render decoration effect into a dedicated effect gated on editor readiness. Removes the mid-effect guard and the per-render handler re-creation, and manages the listener lifecycle with React's own effect cleanup. Also drop the copy chip for value-less (stale/foreign) placeholders so there's no dead button. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add hook-level tests driving the real hook against a fake editor: auto-collapse writes a placeholder edit wrapped in undo stops, chips are drawn for a known placeholder, the copy chip is omitted for a value-less placeholder, and the content-change subscription is disposed on unmount. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec659121ce
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Match the file-per-concern layout the rest of the feature follows
(useVectorEmbeddingCollapse.{types,constants,utils}). Move the interfaces
to vectorEmbeddingPlaceholders.types.ts (barrelled like the sibling types
file), the regex to .constants.ts, and the CSPRNG session-id helper to
.utils.ts; the store file now holds only state and the public methods.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The manual expand and ▾-collapse paths still called executeEdits without undo stops, so a click after editing the vector could merge with that edit and let Ctrl+Z skip the intermediate state. Bracket both with pushUndoStop, symmetric with the auto-collapse path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f55dce40c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
… coords Restoring the pre-edit selection reused columns that are invalid once the long blob became a short placeholder, so the caret could land in later arguments. Let executeEdits remap the caret, then reveal it — which also brings the view back from the bottom after a paste scrolls to the blob's end (replaces the old force-scroll-to-top behaviour). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f014afeab9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The manual expand and ▾-collapse paths restored the pre-edit selection, whose columns are stale once a placeholder swaps with the full vector, so the next keystroke could land inside the blob. The capture-phase handler already prevents the click from moving the caret and executeEdits remaps the existing selection, so the restore was redundant as well as wrong. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

What
Collapse each large vector embedding detected in the query editor (Workbench and Vector Search) into a compact, clickable chip —
▸ vector · N dimsplus a copy button — so long FLOAT32 blobs no longer dominate the editor.▾arrow to re-collapse.Copytooltip on the button.Builds on the detection layer from RI-8317 (#6215, merged).
Testing
\xFLOAT32 blob or a numeric array with ≥ 10 elements (e.g. anFT.SEARCH … PARAMS 2 my_blob "<blob>").▾arrow to collapse again; click the copy button to copy the full value; hover the label for metadata and the copy button for theCopytooltip.Unit tests cover the detection key helper, the placeholder store, and the query-editor context expansion (
npm test).Closes #RI-8326
Note
Medium Risk
Query run and save paths depend on correct placeholder expansion; a bug could send truncated or wrong embedding text to Redis, though behavior is covered by tests and failures leave unknown placeholders visible.
Overview
Collapses detected large vector embeddings in Workbench and Vector Search query editors into compact chips (
▸ vector · N dimsplus copy), wired through newuseVectorEmbeddingCollapseonVectorEmbeddingHighlight.The editor model holds short session-scoped placeholders (
vectorEmbeddingPlaceholders); full blob text lives in an in-memory map keyed by unguessable ids. Users can expand/collapse via chip UI, copy without expanding, and undo/redo is handled so re-collapsing does not fight Ctrl+Z. Copy/cut rewrites clipboard text to expanded values; submit (QueryEditorContext) and query-library save (QueryEditorWrapper) callexpandVectorEmbeddingsso placeholders never reach Redis or persisted queries.Adds
getEmbeddingKeyso PARAMS-named embeddings stay expanded while editing, chip global styles, i18n labels, Monaco test mocks, and unit tests for the hook, placeholder store, and detection edge cases.Reviewed by Cursor Bugbot for commit 6856775. Bugbot is set up for automated code reviews on this repo. Configure here.