Keep Redis hash tags in a single Browser tree node - #6454
Conversation
The Browser Tree view split key names on the configured delimiter with no
awareness of hash tags, so a hash tag spanning several delimiter-separated
groups (`{portal2:co}:something`) was torn apart into a `{portal2` folder
holding `co}` and `tb}` children.
Delimiters that fall inside a hash tag are no longer split points. The hash
tag is resolved exactly like Redis does in `keyHashSlot` (cluster.c): the
first `{`, the first `}` after it, and only when there is at least one
character in between. Keys with no braces, unbalanced braces or an empty
`{}` keep the previous behaviour, as do keys grouped with a prefix length.
Both the tree structure and the leaf labels go through
`splitWithPrefixThreshold`, so the change lives there and in the copy
inlined into `constructKeysToTree`, which is stringified into a Web Worker
Blob and cannot reference module scope.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d169efa99
ℹ️ 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".
A delimiter match rejected for starting inside the prefix length had
already been consumed by the global regex, so an overlapping match just
after the threshold was never found. With delimiter `aa`, prefix length 1
and key `aaa{x}:z`, `/aa/g` matched at index 0, the predicate rejected it,
and the real match at index 1 was skipped — the whole key came back as a
single part instead of `['a', '{x}:z']`.
Setting `lastIndex` to the prefix threshold before scanning fixes it.
A match starting before the threshold was rejected anyway, and one
straddling it is rejected too, so no previously accepted split point
changes; a rejected match simply can no longer swallow a real one.
`partStart` stays at 0 so the first part still includes the prefix.
Only reachable with a self-overlapping multi-character delimiter, a prefix
length landing mid-overlap and a valid hash tag, but it was a behaviour
change against the previous suffix-based split, so it is restored here.
Applied to the copy inlined into constructKeysToTree as well, which is
stringified into a Web Worker Blob and cannot reference module scope.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e91127886e
ℹ️ 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".
A match rejected for sitting inside the hash tag had already advanced the
global regex past its whole length, so an overlapping match starting one
character later was never seen. With delimiters `aa` and `a}` and key
`{aa}:x`, the `aa` at index 1 was rejected for being inside the tag, which
consumed index 2 and hid the eligible `a}` there — the key collapsed to a
single part instead of splitting at the closing brace.
Rejected matches now resume the scan from one character after their start
rather than past their end. Accepted matches still advance normally, every
rejection advances by at least one character so the scan stays O(n), and
the zero-length guard is untouched.
This is the hash-tag counterpart of the prefix-threshold rescan: the two
guards that can reject a match no longer discard input as a side effect.
A `{aa}:x` key now yields `['{a', ':x']`, the documented behaviour for a
delimiter straddling the closing brace.
Applied to the copy inlined into constructKeysToTree as well, which is
stringified into a Web Worker Blob and cannot reference module scope.
Remove the inline comments and doc block added alongside the hash-tag splitting fix. The behaviour is covered by the test descriptions, and the comments obscured the code more than they explained it. The pre-existing notes that constructKeysToTree keeps an inlined copy of splitWithPrefixThreshold for the Web Worker are left in place, as is the existing commentary in constructKeysToTree.spec.ts. Comment-only change: no non-comment line is touched.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 30c83db825
ℹ️ 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".
Code Coverage - Frontend unit tests
Test suite run success8078 tests passing in 873 suites. Report generated by 🧪jest coverage report action from 30c83db |
Requested via Slack thread
What
A Redis hash tag is the part of a key name wrapped in
{...}. Redis uses just that part to decide which cluster slot the key lands in, so keys sharing a hash tag are guaranteed to live on the same node. It is normal for a hash tag to contain the same delimiter used elsewhere in the key, e.g.{portal2:co}:something.The Browser Tree view split key names on the delimiter without knowing about hash tags, so a hash tag containing a delimiter was torn in half:
{portal2:co}:somethingand{portal2:tb}:somethingcollapsed into one{portal2folder holdingco}andtb}children. The folder names were not real key prefixes, and keys that Redis keeps together were shown as if they were unrelated.Now a hash tag is kept in a single tree node, so those keys group under
{portal2:co}and{portal2:tb}. Keys with no hash tag are grouped exactly as before.This was originally reported against the VS Code extension as redis/Redis-for-VS-Code#308, where the same fix shipped in redis/Redis-for-VS-Code#309.
Technical solution
The hash tag is resolved the same way Redis does in
keyHashSlot(cluster.c): the first{, then the first}after it, and only when there is at least one character in between. At most one such span per key. Delimiters inside it are no longer split points; everything else — no braces, unbalanced braces, an empty{}— splits as before.All four places that split a key name (tree structure, leaf labels, auto-expand to the selected key) already go through one shared helper, so the rule lives there.
constructKeysToTreekeeps a copy inlined because it is stringified into a Web Worker Blob and cannot reference module scope; the existing "keep both in sync" note there still applies.This composes with the existing prefix-length setting for free: a prefix length only ever suppresses split points, it never inserts a boundary, so it cannot bisect a hash tag. Worst case it declines to split where it otherwise would — the same direction the hash-tag rule pushes.
Open questions
a{b:c}:d:{e:f}still shows{e/f}— this matches how Redis picks a slot, but may look inconsistent in the tree.{aa}:xwith delimitersaaanda}splits into{a/:x. Deliberately not special-cased.Testing
How to verify by hand
Open Browser, switch to Tree view, delimiter
:. Expect two separate top-level folders{portal2:co}(2 keys) and{portal2:tb}(1 key), with leaf labelsother/something;broken{tag→keyanduser→1→nameunchanged. Onmainthe first three keys instead collapse into a single{portal2folder containingco}andtb}.(images to be added)
Also worth a look: set a prefix length in Tree view settings and confirm the hash tag still is not split.
Automated
27 unit tests pass across the shared helper and the worker-inlined copy, covering each case described above plus unbalanced braces,
{}, multiple delimiters, and the prefix-length interaction from both sides (a prefix landing inside the tag, and one reaching past it).npm run lint:uiis clean.npm run type-checkfor the UI reports 23 errors, but they are pre-existing and unrelated: they are all inredisinsight/ui/src/packages/*, whose own dependencies were not installed in my environment. I verified the error set is byte-identical with and without this change, and none of them are in the touched files. No baseline file was modified.Reported in the VS Code extension as redis/Redis-for-VS-Code#308; same fix shipped there in redis/Redis-for-VS-Code#309.
Note
Low Risk
UI-only key-name parsing for tree grouping; no server or data-path changes, though tree folders for hash-tagged keys will look different for affected users.
Overview
Fixes Browser Tree view splitting keys like
{portal2:co}:somethingon:inside the hash tag, which wrongly merged different tags under a fake{portal2folder.splitWithPrefixThreshold(and the duplicate inlined inconstructKeysToTreefor the Web Worker) now treats the first Redis-style hash tag—first{…}with at least one character inside—as a single segment: configured delimiters inside that span are ignored; everything else (no tag,{}, unbalanced braces) behaves as before. Prefix-length rules still apply and can extend the first folder past the tag without cutting through it.Adds focused unit coverage in
splitWithPrefixThreshold.spec.tsandconstructKeysToTree.spec.tsfor grouping, multi-delimiter patterns, prefix length, and edge cases (overlapping regex matches).Reviewed by Cursor Bugbot for commit 30c83db. Bugbot is set up for automated code reviews on this repo. Configure here.