Skip to content

Redis Workbench - #3862

Open
paoloredis wants to merge 17 commits into
mainfrom
DOC-6997
Open

Redis Workbench#3862
paoloredis wants to merge 17 commits into
mainfrom
DOC-6997

Conversation

@paoloredis

@paoloredis paoloredis commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Note

Medium Risk
Large, user-facing interactive docs surface (workbench, Try it, Thebe/Binder session persistence) with many moving parts; failures affect tutorials and in-browser runs but not core product auth or data paths.

Overview
Introduces an on-page Redis Workbench — a bottom dock with a CLI terminal, key/value browser, command completion (built from command reference pages), and a Notebook pane for runnable Jupyter cells — wired in through a shared cli-assets partial on docs layouts.

Try it no longer auto-executes redis-cli on page load or embeds live terminals in prose. Examples render as static code; clicking Try it runs commands in the workbench (or falls back to redis.io/cli), with page setup from blocks marked prereq="true" so mid-tutorial snippets get indexes/data first. The Try It implementation is consolidated in tryit-script.html.

Jupyter shortcodes swap visible Run here / Run in browser for Try it (workbench notebook, Binder URL fallback), keep hidden driver run buttons, and leave static twins on the page when cells move into the dock.

Thebe/Binder integration in baseof.html is hardened: fixed production binder URL, binder ref/kernel fallbacks from cell attributes, appendToken on restored sessions, server-alive checks before expiring sessions, and safer bootstrap/copy/expand behavior for .thebe-static clones.

Search tutorial pages tag reload/setup examples with prereq="true" so aggregation/search steps seed the sandbox correctly.

Reviewed by Cursor Bugbot for commit 56310f6. Bugbot is set up for automated code reviews on this repo. Configure here.

paoloredis and others added 10 commits August 19, 2026 15:34
"Try it" no longer sends the reader to redis.io/cli. It opens a console docked to
the bottom of the page — a full-width "Workbench" bar that expands into a live
terminal plus a Redis Insight-style key browser — and runs the snippet there.

  layouts/partials/cli-assets.html   loads cli.js + the workbench, in one place
                                     so a new page type picks up all of it
  static/js/redis-workbench.js       the dock
  static/css/redis-workbench.css     its styles, all scoped under .rwb
  layouts/partials/tabs/wrapper.html the button prefers the dock, and falls back
  layouts/shortcodes/redis-cli.html  to opening redis.io/cli as before

The dock is one per page, not one per snippet: each "Try it" runs into the same
session on top of what is already there, keys and transcript accumulating, so a
reader can follow a page's examples in order and watch the keyspace build up.
"Clear keys" (FLUSHDB, transcript with it) and "Clear terminal" are the ways back.

Keys are discovered, not scanned. The sandbox database is shared by every visitor
(~300k keys) and the backend rewrites SCAN to MATCH <session>:*, which Redis
applies after walking the table — one iteration at COUNT 10000 comes back empty,
so enumerating a handful of keys would take dozens of round trips. Instead the
dock asks the server where each executed command put its keys (COMMAND GETKEYS)
and tracks those names; indexes come from FT._LIST, which the backend scopes to
the session. Nothing is probed until the reader opens the panel: batches that run
while it is closed are only remembered, and discovery catches up in one pass.

Value views read the way someone would read by hand — GET, HGETALL, SMEMBERS, the
whole LRANGE — and only fall back to a bounded or cursor form when the value is
too big to show, saying what it left out. That matters because the commands are
shown to the reader ("Read with"), which is the part of the browser that teaches.

Two things worth knowing:

  * The dock needs window.RedisCli, which the /cli backend's widget only
    publishes from an unreleased version (see the redis-clinterwebz branch of the
    same name). Against production it stays dormant and "Try it" behaves exactly
    as it does today, so this is safe to ship ahead of that backend.

  * static/js/cli.js therefore points a LOCALLY served docs site at a local
    backend by default — marked TEMPORARY, with the one line to delete once the
    backend ships. Until then, `hugo server` without that backend running has no
    working terminals.

Verified in a headless browser against the real sandbox: all thirteen key types
render, the bar spans the viewport and reserves its strip so the footer is not
covered, the panel clears the sticky header at full height (both learned the hard
way — the footer is z-50 and the header sticky z-50), drag and keyboard resize,
state persists across pages, and the old-backend fallback still opens the
standalone CLI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a Notebook pane to the workbench dock for pages built from the
jupyter-example shortcode (/develop/clients/redis-py and friends), and
moves running out of the page and into it.

The page goes back to being ordinary code blocks. Thebe rewrites what it
activates — highlighted code becomes an editor with its own run/restart
controls — so the runnable cell is adopted into the dock at load and the
page keeps a plain clone of the block (.thebe-static), with one "Try it"
that opens the dock on the Notebook tab. "Run here" and "Run in browser"
are gone; Try it falls back to opening the BinderHub notebook where the
CLI widget has no workbench API. The full-file view is a single unmarked
listing: rendering it per section to highlight the current step drew a
band over part of the file and gave each section its own padding, which
read as blank lines the file does not have.

The pane reads Thebe's own record rather than guessing. An output wrapper
appears when a request is SENT, the driver clears its `loading` class on
every queued cell as soon as any one goes idle, and an editor exists
before a kernel is ever asked for — all of which had cells ticked off as
run when the connection had dropped. `executionCount` from the kernel's
execute_reply is the one signal that means "this ran", and it supplies
the In[n]/Out[n] a real notebook would print. A dropped execution says so
instead of showing a count.

Run all hands the whole queue to the kernel instead of waiting for each
cell in turn, and Clear notebook goes through Thebe's cell.clear() —
emptying the container detaches the widget Thebe renders into, so every
later result landed in an orphaned node.

Two fixes to the Thebe integration behind "if I refresh, keep the
kernel":

  - saveSession() stores no appendToken, so on restore makeSettings()
    recomputes it as false (page and server share a hostname through
    /jupyterhub/) and the kernel websocket goes out without its token.
    The user server refuses cookie-only auth for a websocket, so the
    handshake was rejected and retried forever: a kernel that reported
    ready and could not run anything. The flag now goes into the saved
    record, which makeSettings spreads over its own default.

  - The session was discarded on any "disconnected"/"Connection lost"
    signal, including transient ones, so a refresh launched a new pod.
    Expiry now asks the server first, with redirect: 'manual' — a pod
    that is gone answers 302 to a login page, which a followed redirect
    reports as alive.

Clear keys and Clear terminal move into the Terminal pane, beside what
they act on, as Run all and Clear notebook are in the Notebook pane.

build/dev-proxy.js serves the docs and the hub from one origin for local
work, since /binder/build/* sends no CORS headers and an EventSource
cannot be given an Authorization header. Credentials come from the
environment only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ning commands

The dock's Terminal tab is now a three-column split: the CLI, the keys it
writes, and the value of whichever key is selected. The keys used to be a
sidebar shown beside every pane — including the notebook's, whose kernel
talks to a different Redis entirely — and the value hid behind its own
tab, so reading a key meant leaving the terminal that had just written it.
The Value tab is gone, the notebook gets the full width, and a divider
between each pair drags (and takes arrow keys), with the proportions
remembered alongside the height.

Columns grow from a zero basis rather than sitting at fixed percentages:
three at "0 0 N%" summing to 100 plus two dividers add up to more than
there is, and the last column overflowed the dock.

"Clear keys" only clears keys now. It used to wipe the transcript too,
which is what the button next to it is for; the FLUSHDB still goes through
the terminal, so the transcript records why the keys went away.

The dock has a gutter down both sides, applied on the root so the
background and top border still span the page while everything inside is
inset. That exposed a latent bug: .rwb-facts had flex-wrap but no
min-width: 0, and a flex item's min-width is `auto` — so it never shrank,
its own wrap never applied, and the last badge was clipped.

The redis-cli shortcode and the redis-cli tab of clients-example render a
static code block instead of a live terminal. They used to emit
form.redis-cli, which cli.js executed as the page loaded: reading a page
of prose ran commands against a shared sandbox whether or not the reader
wanted it, and what was on screen was the sandbox's output rather than the
example as written. Running happens in the workbench now, behind "Try it",
where the reader asks for it and can watch the keys it creates. Loading
/develop/data-types/strings/ issues no command batches at all.

runnable="false" still means no "Try it" (unsafe blocking commands like
BRPOP), try_it="false" still hides the button on its own, and copy still
works — codetabs.js already read div.redis-cli-static > pre.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lbars

Key-type badges use Redis Insight's own colours, so a type reads the same
here as in the tool a reader is likely to open next. Taken from its source
rather than guessed at a screenshot: GROUP_TYPES_COLORS in
redisinsight/ui/src/constants/keys.ts maps each type to a CSS variable,
resolved in styles/themes/dark_theme/_theme_color.scss
(redis/RedisInsight@main). Labels turn white, which is the pairing those
fills are designed for.

Two of ours have no Insight equivalent. The probabilistic types (Bloom,
cuckoo, count-min, top-k, t-digest) get --defaultTypeColor, which is what
Insight's badge falls back to for a type it has no colour for, so they
look the same there as here. A search index is not a key at all, so it
takes --typeGraphColor: the one colour in the palette no type we list
uses.

The terminal follows its output again. The widget scrolls its own box when
it runs standalone, but in the dock that box is told to grow and the CLI
column does the scrolling, so nothing was pinning it to the end: a snippet
appended to a full transcript landed below the fold and the reader had to
go looking for what their click had done. Now "Try it" pins to the end —
it is a request to see a result — and typing follows the end too, while
someone who has scrolled up to re-read an earlier reply is left alone
until they come back. Resizing keeps the last line in view.

Scrollbars in the dock are thin, dark and trackless, scoped so the docs'
own are untouched. Not black, though it was asked for and tried: on the
dock's #0f172a surface a black thumb cannot be seen, so this is
--rwb-bg-3, the slate already used for raised surfaces. The standard
scrollbar-width/-color come first because Blink and Firefox use them —
including for the overlay scrollbars macOS Chrome shows, which
::-webkit-scrollbar cannot colour — and the explicit 7px rounded thumb
still applies in Safari and older Blink.

Also: the key browser forgets names that no longer exist. A deleted or
expired key stayed on the list to describe and was re-probed on every
later sweep, spending commands on a shared backend for a key that is not
there. The sweep's own result says which survived; the list is snapshotted
first so a batch landing mid-sweep is not mistaken for a key that vanished.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The key browser said several things that were true of the sandbox and of
nowhere else, which a reader comparing notes with their own terminal would
read as the docs being wrong:

  - MEMORY USAGE. Every key here is stored as `<session-uuid>:<key>`, so
    it counted 37 bytes of prefix: 72 B where redis-cli and Redis Insight
    both say 40 B for `SET bike:1 Deimos`. The gap is not a constant to
    subtract — 72 against 40 because the longer name crosses a jemalloc
    size class — so the figure is gone rather than corrected.
  - OBJECT ENCODING. The same value that a plain SET stores as `embstr`
    comes back `raw` through this backend, verified against the object
    itself, so it is the write path rather than a mangled reply. Also
    gone; the cause is a question for redis-clinterwebz.
  - The remaining size chip was a bare "6 B" that read as the key's
    footprint when it is the length of the value. It now reads
    "Length: 6", with Insight's own field names from
    browser.keyDetails.length.* — Length, Entries for a stream, Samples
    for a time series — and every chip says on hover which command
    produced it.

TTLs count down where they are shown, and a key that expires leaves the
list on its own. The countdown is arithmetic on the TTL the last sweep
read and the moment it read it; asking the server every second would
spend a command per key per tick to learn what subtraction already knows.
At zero the row goes and one sweep follows to confirm it — which also puts
the row back if the key outlived our clock. Only the TTL text is
rewritten, never the row, so it cannot fight the pointer.

Arrays and vector sets are now first-class rather than unknown types
falling through to "no enumerable value":

  - An array badges in Insight's --typeArrayColor and previews as index
    and element via ARSCAN, which skips empty slots — ARGETRANGE over
    `ARSET a 1000000 x` would answer with a million nils. ARLEN gives the
    logical length, ARCOUNT the number that exist.
  - A vector set carries Quant type and Vector dim from VINFO beside its
    length, lists its elements (VRANDMEMBER, sorted so the same set does
    not reshuffle on each open), and each element opens onto its vector
    and attributes — VEMB and VGETATTR, the pair behind Insight's
    magnifier. That view survives a command now: a sweep re-reads the open
    element instead of replacing it with the key's own view, so a
    VSETATTR shows through where it used to close the panel.

A list's preview says Index / Element, the word the docs beside it use
sixty-six times against nineteen for "items".

Redis Insight shows nothing at all for a time series — it points at its
Workbench — but a reader who has just run TS.ADD is better served by the
samples, so that view stays and only borrows the label.

The notebook cells drop the step name and the "needs connect" chip. The
name is a build-time identifier from examples.json and the prose above
each cell already says what it does; the gate is on the Run button, which
is inert and explains itself on hover. The name stays as data-cell,
because the depends rules are written in those names.

Also gone: the "Full CLI" link, and a dead vectorset probe that a fuller
one had shadowed since it was added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Type the start of a command in the workbench terminal and the matches
appear with the syntax each one takes; choose with the arrow keys, insert
with Enter or Tab. Once a command is settled, its remaining arguments
trail the cursor dimmed, the way redis-cli hints them.

The list is the docs' own. Every content/commands/*.md already carries the
syntax line its page prints (syntax_fmt), a summary and a group, so a new
partial publishes those 583 commands as one file and the completion cannot
drift from the reference beside it. It is fetched on the first keystroke
and cached, so a reader who never types pays nothing.

Matching follows what Insight offers rather than a simpler rule that would
have been easier to write: exact first, then prefix, then subsequence — so
"hget" reaches HMGET, whose letters appear in order though not as a
substring, and "xinfostr" reaches XINFO STREAM. Matched letters are marked
individually. Separators are ignored, which is what makes "jsonget" find
JSON.GET.

A space settles the command, and only a subcommand can follow it: "XINFO "
offers its four, "ACL " its thirteen, and "HDEL " offers nothing, because
HDEL takes none. Getting this wrong is what put the picker back on screen
every time a space was typed after choosing a command — the subsequence
tier ignores separators, so it still matched the command just chosen.

The inline hint counts arguments as redis-cli does: a part-typed argument
counts, so `hget bike` hints "field" rather than "key field", and a
trailing repeating group stays hinted while the reader is inside it. It is
a span laid over the prompt line, because an <input> cannot hold styled
text of its own and the terminal belongs to the /cli widget: everything
here attaches from outside it. user-select is off, or a reader copying
their session would take the syntax hints with it.

Placement never covers the line being typed. Below the prompt if it fits,
above if it fits there, and on the roomier side capped and scrolling when
neither does — a dock at its minimum height is 63px of terminal. A
ResizeObserver on the column re-places it, so growing the dock while the
list is open gives the list the new room instead of leaving it capped.

Two bugs found by looking rather than by the suite passing: the first
keystroke of a visit is what fetches the list, so the hint drew before it
arrived and showed nothing until another key followed; and "\_" in
syntax_fmt is a YAML escape that arrives as U+00A0, so SET's
"IFEQ\_ifeq-value" needed flattening.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The list was being clipped rather than capped. Shrinking the dock could leave
the prompt below the fold, and a list placed against a prompt nobody can see
took its room from a figure larger than the column: the top rows went outside
the column, which hides its overflow silently, so typing SIN showed the loose
matches at the end of the list and no scrollbar to say SINTER was above them.

The prompt is brought back into view first, both rooms are measured inside the
visible slice of the column, and the cap is written on every placement — left
unwritten, a cap from a short dock survived the dock being grown. The one-row
floor is gone with it: it was what pushed the list past the column's edge.

Placement also re-runs on scroll and settles across frames, because a resize
clamps the column's scroll a frame later with no scroll event to hear, and it
measures the content instead of clearing the cap and re-measuring inside a
ResizeObserver — that forced re-layout is the loop Blink cuts short by dropping
the notifications after it. The list scrolls back to the top so a cap never
leaves it showing its tail, and "N more — keep typing" sticks to the foot so a
scrolling list still says there is more.

And the keys pane drops "Length: N" from its rows: it is on the value beside
them, and a list of keys is for finding one, not for reading it twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things a reader could not do before: read an index, find it, and see what
it holds.

Reading it. FT.INFO was dumped as a Field/Value table of whichever internal
counters seemed useful, nested lists and all. It is now Insight's "View index"
panel: the definition as one sentence ("Indexing JSON documents whose keys start
with "product:"."), the schema as a table of identifier, attribute and type with
Weight only when a field carries one, and the counts Insight puts under it. Field
types are badged in the order Insight badges them — TEXT informative, TAG notice,
NUMERIC attention, VECTOR success, GEO default — with the nearest hues in the
palette the dock already uses, since those variants resolve inside the redis-ui
package rather than in the app. Flags like SORTABLE sit beside the type they
qualify rather than in a fifth column that a narrow pane would push off the edge.

Finding it. Indexes were rows under a heading in the middle of the key list,
which read as a key called "Indexes" and scrolled against the keys above it — on
a search page, an index sat below the fold of the dozen documents it indexed.
KEYS and INDEXES are now two sections of that column, each with its own count and
its own scroll, and the divider between them drags like the ones between the
columns. The section appears once the session has an index: most pages never
create one, and an empty box with a heading would take a third of a list that a
short dock has little enough of.

Seeing what it holds. Selecting an index narrows the key list to its documents,
as Insight's index selector does, with a chip in the KEYS heading saying so and
clearing it. The documents come from FT.SEARCH in the sweep's own batch, and ones
the dock never watched a command touch are adopted, so the list is the index's
contents rather than the part of them this session happened to see. Any command
the reader runs drops the filter — what they just wrote is what they want to see —
and so does dropping the index, flushing the sandbox, or clicking the chip.

Two bugs fixed on the way. A sweep used to replace an open index with "Select a
key to see its value", because it only knew how to re-open keys; it re-reads the
index instead, which also keeps its counts current as documents arrive. And the
sandbox strips its session prefix out of FT.SEARCH replies assuming the
with-fields layout, stepping two elements at a time, so a NOCONTENT reply comes
back with every other name still prefixed — those are stripped here rather than
matched against the key list and missed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Try it" on step 3 of the search tutorial ran FT.SEARCH against an index the
sandbox had never been told to build, so the reader got SEARCH_INDEX_NOT_FOUND
where the page prints a count. The commands to fix that were already on the
page — every step keeps a "Reload products data and re-create index" block in a
<details> — but nothing connected the two.

A block marked prereq="true" now publishes its commands as the page's setup, and
every other "Try it" there runs them first. Once per sandbox session, not per
click: re-creating an index and twelve documents on every snippet would put a
wall of output between the reader and what they asked for. Clicking the setup
block itself counts, so nobody loads the data twice; "Clear keys" empties the
sandbox and forgets; a run that fails does not claim to have happened. The
status line says "setting this page up first" and the commands appear in the
transcript, so nothing happens invisibly.

openTryItCli was two inline copies — one in the redis-cli shortcode, one in
tabs/wrapper.html — both guarded with `|| function`, so which one a page got
depended on which shortcode Hugo rendered first, and only one of them knew about
prerequisites at all. They now share layouts/partials/tryit-script.html. The
older per-set needs_prereq model still works for pages that use it without a
page-level setup.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things went wrong in the notebook pane, all of them in what the reader
could see.

The toolbar. It was sticky inside the scrolling pane, resting at the pane's
padding edge — 8px short of the top, so cell content scrolled through the strip
above it. Moving the padding onto the toolbar fixed that until the cells ran:
a cell that has executed carries Thebe's own editor and output widgets, and
those paint over a sticky sibling however it is layered, so the toolbar appeared
to go transparent with code sliding through it. It is no longer in the scroller
at all — the pane is a flex column with a fixed toolbar row and the cells
scrolling beneath it, which nothing in the scroller can paint over.

"No response" on a cell whose result was on screen. The last cell of the
node-redis notebook prints nothing by the page's reckoning, so it has no output
container — but the JavaScript kernel echoes the value of an awaited expression,
and `await client.quit()` answers OK into Thebe's own output area. The pane
looked only in its own container, found nothing, and called the kernel silent.
It now reads either place, and waits another second and a half before saying
anything: the execution count is recorded after the future resolves, so it and
its output can both land after the cell's turn looks over. A cell with output and
no number ran — the output is the evidence. "No response" is left for a cell that
produced neither.

Scrolling. A cell's inner boxes overflow their own frames, which browsers treat
differently for the wheel: scrollable-in-the-DOM but not by the reader. Chrome
passes the event to the scroller behind; where it does not, the notebook stops
moving a few cells from the end. The scroller takes the wheel first and hands it
back only if what is under the pointer has room left in that direction.

Also: the two footer notes are gone, and the display rule for the notebook pane
is scoped with :not(.rwb-hidden) — without that it outranked the rule that hides
an inactive pane, and the notebook rendered under the terminal, squashing it to
a single line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

DOC-6997

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

The dev proxy is a local tool and does not belong in the repo, so nothing here
should point at a path that is not in it. Two comments and one status line now
say what is needed — the docs served from an origin that also has /binder/ —
instead of naming the script that does it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The CLI shim defaulted to a backend on localhost:5000 when the page was served
locally, and the Thebe config let a local page aim at another BinderHub through
a query parameter, a localStorage key, or a flag a dev proxy set. All of it was
hostname-gated and inert on redis.io, and all of it was local-development
plumbing living in the shipped files.

Both now name one endpoint: https://redis.io/cli and https://redis.io/binder/.
The cross-origin warning goes with them — it only ever fired on a local page
aimed elsewhere, which is no longer a thing this code does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread static/js/redis-workbench.js
Comment thread static/js/redis-workbench.js
Comment thread layouts/_default/baseof.html
Comment thread layouts/_default/baseof.html
All four were real; each is a case where the code assumed something the DOM or
the run had not got round to yet.

A first "Try it" handed its batch to a terminal that was still starting.
createCli is async, and open() dropped its promise, so the very first click
raced the terminal's own startup — and RedisWorkbench.open had already returned
true, so the standalone-CLI fallback could not step in either. The promise is
kept now and the batch waits for it.

A setup whose run failed was remembered as done. Clicking the setup block's own
"Try it" claimed setupRan[provides] before running, and the failure path only
took back a claim made by the prepending branch. A snippet that needed the setup
afterwards would skip it and search an index that was never built. Both claims
are tracked the same way and both are given back.

Copy threw on the static twin. copyJupyterCodeToClipboard resolved
.thebe-container only, while the visibility toggle and the link copy beside it
already accepted .thebe-static — which is what the page keeps once the workbench
adopts a runnable cell. Same selector as its neighbours now, and a null guard
behind it.

"Reconnecting" was never taken off. expireSessionIfDead adds it to every
container when it keeps a session the server still answers for, and nothing
removed it: the workbench reads that class before any other, so the header stayed
on "kernel reconnecting…" with a working kernel behind it. The attached handler
clears it alongside marking the containers ready.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread static/js/redis-workbench.js
Comment thread static/css/redis-workbench.css
Two more from the review.

The setup block is labelled "Reload products data and re-create index", and it
starts with FT.CREATE. Once an earlier "Try it" had applied the setup for the
reader, clicking that block answered with "Index already exists" — the reload it
offers, reporting an error. It now drops the indexes it is about to re-create
first: exactly those, parsed from the setup's own commands, and only the ones
this session actually has, so neither the drop nor the create has anything to
complain about. Its documents overwrite themselves as they always did. A first
run drops nothing, because there is nothing to drop.

And the driver's own messages about the session — "please refresh the page", the
rate-limit notice — go into each cell's codetabs header, which the pane hides
along with the rest of the page's chrome. So a reader whose kernel had died was
told nothing, in the one place they were looking. The notebook mirrors whatever
is there above its cells, and stops when the driver takes it away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread content/develop/get-started/search-tutorial/data-modeling.md
The data-modeling step writes product:1 in the example that introduces JSON.SET
and product:2 onwards in the block that loads the rest, so marking only the
second one as the page's setup left the first document out: a reader clicking
"read one document back" on a fresh session asked for product:1 and got nil,
where the page prints its name.

A page can now mark as many blocks as its setup takes. They register as parts in
document order, each remembered on its own, and a "Try it" runs the parts this
sandbox has not had — so nothing is replayed twice and nothing is missing. A part
clicked directly runs only itself: it *is* setup, so it needs none. A failure
gives back every part that run had claimed, not just one.

Only data-modeling has a split setup today; the other four pages keep their
single block and behave exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@paoloredis
paoloredis requested a review from a team August 27, 2026 08:42
@andy-stark-redis

Copy link
Copy Markdown
Contributor

@paoloredis Looking very good :-) Just a few things come to mind so far:

  • Clicking any point on the title bar closes/opens the Workbench, but there's also a "^" button, which seems redundant. Also the slide up/down cursor shows at the bottom of the title bar. Maybe remove the "^" button and show the slide up/down cursor at the top of the title bar (which is where I'd usually expect to see it) or disable the show/hide behaviour when you click the title bar and instead use the bar itself for sliding the Workbench up/down.
  • Maybe have an "are you sure" check on the Clear Keys button, or have an extra Restart Example button. Currently, if you clear the keys by mistake, you have to stop and find the Try It button on the example box again to get it back.
  • Really great that you can run notebooks from the Workbench too :-) However, the Terminal tab shows as clickable even when it's the only option. Also, some pages (like the aggregation tutorial) have a client language available but no notebook. Here, the Try It button is disabled on the example box but the Workbench still shows with only the Terminal tab. When there's a client language available in the snippet, but without a notebook, maybe grey out the Notebook tab in the Workbench or show a message inside the tab saying "No notebook available for this example".
  • For the data type pages:
    • The first three example snippets on the Lists page don't seem to show any keys in the keys column. Other examples work fine.
    • Just checking that you intentionally aren't supporting Streams for now?
    • HyperLogLogs are implemented on top of Strings and geospatial on top of Sorted Sets. The Keys column shows the "base" type (eg, String for HyperLogLog). This is also the behaviour of the type command, so there might not be anything we can do about it, but we might need to mention this more explicitly in the page text with a note above the first example or something.

These are just minor things, though. I'm happy to approve it as it is - really great work!

@andy-stark-redis andy-stark-redis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved (but see comments for a few thoughts). I'll re-add docs as reviewers so you can get everyone else's thoughts about this.

@andy-stark-redis
andy-stark-redis requested a review from a team August 27, 2026 10:19
… clearing

Three unrelated things, all about what the reader is looking at.

The key list waited for the last answer before drawing anything. Describing a
key takes three round trips the sandbox cannot fold together — which keys did
those commands touch, what are they, how long are they — and against the
deployment that is ~300ms each. So the list is drawn as the answers arrive: a
placeholder row per name as soon as COMMAND GETKEYS reports it, then the real
row when TYPE and TTL land. The sizes no longer hold it up at all, since Length
moved to the value pane. Measured with the deployment's latency: a key appears at
~640ms and is described at ~940ms, where nothing appeared before ~1250ms. Same
requests; only the waiting changed.

The resize grip was between the title bar and the panel, so the resize cursor
showed along the bottom of the bar while the dock's top edge — the one a panel
that grows upwards is grabbed by — did nothing. It is on that edge now, and
hidden while the dock is shut, where there is nothing to resize.

And "Clear keys" asks first. It is the one control here that destroys something,
what it destroys took a snippet to make, and a reader who hit it by accident had
to go back up the page to find the "Try it" that filled the sandbox. The question
appears in the toolbar the button came from rather than in a browser dialog, with
the confirm focused for the keyboard; Escape or Cancel leaves everything alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread layouts/partials/tryit-script.html
A page whose examples are all notebook cells still got a "Terminal" tab, and
since it was the only tab it looked like a control that did nothing. Worse, the
pane behind it offered a key browser for a sandbox those cells never touch —
they run in a Jupyter kernel with its own Redis.

So the terminal pane is built only where the page has commands for it: a CLI
block, or a "Try it" that is not the notebook's (a client page's Try it carries
.thebe-tryit and opens a cell). Where it is absent the dock also asks the sandbox
nothing at all, instead of sweeping a keyspace that cannot have keys — one fewer
round trip and one fewer session minted on every client page.

And the tab strip appears only when there is a choice to make. One pane needs no
tabs, whichever pane it is. Nothing in the docs ships both today; a snippet
arriving through RedisWorkbench.open still mounts a terminal on demand, and the
tabs turn up with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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 56310f6. Configure here.

var thebe = window.thebe;
var cells = thebe && thebe.notebook && thebe.notebook.cells;
return cells && cells.length ? cells : null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notebook reads the wrong Thebe global

Medium Severity

The notebook pane looks up window.thebe for execution counts, kernel id, and cell-idle events, but this site loads Thebe 0.9.0-rc.12, which exposes thebelab and never assigns window.thebe. Count tracking, refresh gating, and restart detection therefore always take the weak DOM fallback the file itself describes as unreliable.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 56310f6. Configure here.

@paoloredis

Copy link
Copy Markdown
Collaborator Author

Thanks for the feedback @andy-stark-redis ,

Clicking any point on the title bar closes/opens the Workbench, but there's also a "^" button, which seems redundant. Also the slide up/down cursor shows at the bottom of the title bar. Maybe remove the "^" button and show the slide up/down cursor at the top of the title bar (which is where I'd usually expect to see it) or disable the show/hide behaviour when you click the title bar and instead use the bar itself for sliding the Workbench up/down.

I've moved the slide up/down cursor to the top of bar, it does make more sense this way. I'm not sure on removing the ^ button though, I think it is still useful because it is not obvious that when collapsed the workbench is clickable.

Maybe have an "are you sure" check on the Clear Keys button, or have an extra Restart Example button. Currently, if you clear the keys by mistake, you have to stop and find the Try It button on the example box again to get it back.

I've added the "Are you sure" check on the clear button, that's a good idea. I'm not sure on the "Restart Example" button, how would that work when there are several code snippets, which one would it re-run?

Really great that you can run notebooks from the Workbench too :-) However, the Terminal tab shows as clickable even when it's the only option.

I've now removed the Terminal from the pages that have the Jupyter Notebook example.

Also, some pages (like the aggregation tutorial) have a client language available but no notebook. Here, the Try It button is disabled on the example box but the Workbench still shows with only the Terminal tab. When there's a client language available in the snippet, but without a notebook, maybe grey out the Notebook tab in the Workbench or show a message inside the tab saying "No notebook available for this example".

What about hiding the Workbench if any client language is selected?

For the data type pages:
The first three example snippets on the Lists page don't seem to show any keys in the keys column. Other examples work fine.
Just checking that you intentionally aren't supporting Streams for now?
HyperLogLogs are implemented on top of Strings and geospatial on top of Sorted Sets. The Keys column shows the "base" type (eg, String for HyperLogLog). This is also the behaviour of the type command, so there might not be anything we can do about it, but we might need to mention this more explicitly in the page text with a note above the first example or something.

  • In those Lists examples the list is popped immediately so by the time they are finished running the key is empty so it is not shown.
  • Correct, I'll add support for Streams later (there are still some dependencies across code blocks in there that have to be resolved first).
  • Yes that's right I made sure to match what Redis Insight uses for them

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