Skip to content

Keep Redis hash tags in a single Browser tree node - #6454

Merged
pawelangelow merged 4 commits into
mainfrom
fe/bugfix/browser-tree-hash-tags
Aug 31, 2026
Merged

pawelangelow merged 4 commits into
mainfrom
fe/bugfix/browser-tree-hash-tags

Conversation

@claude

@claude claude Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

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}:something and {portal2:tb}:something collapsed into one {portal2 folder holding co} and tb} 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.

Before After
image image

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. constructKeysToTree keeps 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

  1. Should later brace pairs also be atomic? Today only the first is, so a{b:c}:d:{e:f} still shows {e / f} — this matches how Redis picks a slot, but may look inconsistent in the tree.
  2. Should this be opt-in via a Tree view setting rather than always on? It changes existing users' grouping silently, though only for keys that were grouped wrongly before.
  3. Accepted behaviour, not a question: a multi-character delimiter that straddles the closing brace of a hash tag is still a split point — {aa}:x with delimiters aa and a} splits into {a / :x. Deliberately not special-cased.

Testing

How to verify by hand

SET {portal2:co}:something 1
SET {portal2:co}:other 1
SET {portal2:tb}:something 1
SET broken{tag:key 1
SET user:1:name 1

Open Browser, switch to Tree view, delimiter :. Expect two separate top-level folders {portal2:co} (2 keys) and {portal2:tb} (1 key), with leaf labels other / something; broken{tagkey and user1name unchanged. On main the first three keys instead collapse into a single {portal2 folder containing co} and tb}.

(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:ui is clean.

npm run type-check for the UI reports 23 errors, but they are pre-existing and unrelated: they are all in redisinsight/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}:something on : inside the hash tag, which wrongly merged different tags under a fake {portal2 folder.

splitWithPrefixThreshold (and the duplicate inlined in constructKeysToTree for 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.ts and constructKeysToTree.spec.ts for 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.

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.
@claude
claude Bot requested a review from a team as a code owner August 28, 2026 12:19

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread redisinsight/ui/src/helpers/splitWithPrefixThreshold.ts
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread redisinsight/ui/src/helpers/splitWithPrefixThreshold.ts
claude added 2 commits August 28, 2026 12:58
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread redisinsight/ui/src/helpers/splitWithPrefixThreshold.ts
@github-actions

Copy link
Copy Markdown
Contributor

Code Coverage - Frontend unit tests

St.
Category Percentage Covered / Total
🟢 Statements 83.58% 28914/34593
🟡 Branches 69.69% 12312/17666
🟡 Functions 78.64% 7626/9697
🟢 Lines 84.04% 28107/33443

Test suite run success

8078 tests passing in 873 suites.

Report generated by 🧪jest coverage report action from 30c83db

@pawelangelow
pawelangelow merged commit 74cfada into main Aug 31, 2026
21 checks passed
@pawelangelow
pawelangelow deleted the fe/bugfix/browser-tree-hash-tags branch August 31, 2026 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants