Web UI: duplicate a tab on middle/Ctrl/Shift/Cmd click of its title#109357
Conversation
…e title Middle-clicking the tab title, or Ctrl/Shift/Cmd+clicking it (the usual browser gesture for "open in a new tab"), now duplicates the tab. The copy carries over the query and parameters, is inserted right after the source tab, and becomes active. The duplicate's title is the source title with a " N" suffix, N starting at 1 and incrementing until unique. An existing trailing numeric suffix on the source is stripped first, so duplicating "Query A 2" yields "Query A 3" rather than "Query A 2 1". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Duplicating a tab with a middle click focuses the query editor. On Linux/X11 the middle button additionally pastes the PRIMARY selection into the freshly focused editor, appending clipboard junk after the duplicated query. Guard against exactly that one paste, mirroring the mechanism in #109246: `activateTab` arms `suppress_middleclick_paste` around `query_area.focus()` for a genuine middle click, the editor's `paste` handler swallows that paste, and a real keyboard paste shortcut (Ctrl/Cmd+V or Shift+Insert) disarms the guard first so it is never eaten. A short fallback timer bounds how long a never-consumed guard can linger. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Workflow [PR], commit [527ce10] Summary: ✅
AI ReviewSummaryThis PR adds tab duplication in Final Verdict
|
`duplicateTitle` collapsed any trailing ` N` on the source title before numbering the copy, so it also rewrote ordinary user-authored names that merely end in digits: duplicating `Incident 2025` produced `Incident 1`, and `Release 24 7` produced `Release 24 1`. Now the trailing ` N` is collapsed only when it is a real duplicate-family suffix — the title with it removed is itself an existing tab (the family root). So with a `Query A` tab present, duplicating `Query A 2` still yields `Query A 3`, but `Incident 2025` (no `Incident` tab) is kept whole and becomes `Incident 2025 1`, never corrupting a meaningful numeric ending. Addresses the AI review on #109357. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Feedback from GPT-5.5: |
Address review feedback from @pamarcos (GPT-5.5) and the AI review on #109357. `duplicateTitle` used to strip a trailing ` N` from the source title when the stripped base was itself an open tab, to keep a duplicate family flat (`Query A 2` -> `Query A 3`). But a title-only heuristic cannot distinguish a duplicate-family suffix from user-authored trailing digits: with both `Incident` and `Incident 2025` open, duplicating `Incident 2025` stripped ` 2025`, saw `Incident` was taken, and produced `Incident 1` - silently dropping the authored `2025` and joining the unrelated `Incident` family. Stop stripping. The whole source title is now always the family base, so the copy of `Incident 2025` is `Incident 2025 1` and `Release 24 7` is `Release 24 7 1`. This keeps the duplicate lineage unambiguous and never rewrites a meaningful numeric ending. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — fixed in 793109b. |
The duplicate gestures (middle-click, Ctrl/Shift/Cmd+click) could fire while the tab title span was still in inline `contentEditable` edit mode. Because `duplicateTab` copies `src.title`, which is only committed on the editor's `blur`, the copy was built from the stale, not-yet-committed title: renaming `Query A` to `Foo` and duplicating before blurring created `Query A 1` instead of `Foo 1`. Guard all three handlers on `titleEl.isContentEditable`: - `click`: while editing, return early so the click places the cursor or extends the selection instead of duplicating. - `auxclick`/`mousedown`: while editing, do not intercept the middle click, so it pastes the X11 PRIMARY selection into the title as usual. This also stops a plain click from re-triggering `startTitleEdit` (which re-selects the whole title) while already editing. AI Review: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=109357&sha=793109b5&name_0=PR Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… tab Address review: the inline title editor writes back to `tab.title` only on `blur`, but the duplicate path (`duplicateTab`) rerenders the tab bar and destroys the editable span. For a middle click, the `mousedown` handler also calls `preventDefault`, which suppresses the automatic `blur`. So renaming one tab and then middle-clicking a different tab's title to duplicate it silently reverted the first tab's rename to its old title. Fix: expose the active editor's commit function via `activeTitleCommit` and flush any pending rename at the start of `duplicateTab`, before the tab bar is rerendered. The editable tab is always the active tab, so this works regardless of which tab is being duplicated. The `blur` listener's `done` guard prevents a double commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on duplicate Address review: the previous fix only flushed a pending inline title rename (`activeTitleCommit`) inside `duplicateTab`, but any tab-bar rerender destroys the focused `contentEditable` title span before its `blur` commits. The concrete case flagged by review: start a slow query in the active tab, rename it (leave the editor open), and when the response finishes `writeHistoryEntry` calls `renderTabBar`, rebuilding the bar and silently reverting the rename. Centralize the flush at the start of `renderTabBar` - the single rerender chokepoint every path goes through - so a commit-or-lose can no longer be missed. It is safe against re-entry because `commit` clears `activeTitleCommit` before it calls back into `renderTabBar`. The explicit flush in `duplicateTab` stays, since it must run before `duplicateTitle(src.title)` reads the title. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Feedback from GPT-5.5: |
|
Thanks — updated the PR description accordingly. It now documents the shipped behavior: the full source title is always the base, so |
Address review (clickhouse-gh AI review, PR #109357): the disarm path for `suppress_middleclick_paste` keyed off `e.key === 'v' || e.key === 'V'`, which is layout-dependent. On a non-Latin keyboard layout (e.g. Russian), a genuine `Ctrl+V` / `Cmd+V` paste reports a non-`v` `e.key` (the Cyrillic character), so the guard was never disarmed and the subsequent real `paste` event was swallowed - breaking the PR's own guarantee that normal keyboard paste is unaffected after duplicating a tab. Detect the physical `V` key via `e.code === 'KeyV'` instead of the printed character. This is layout-independent and matches how the OS binds the paste shortcut on non-Latin layouts. The `Shift+Insert` branch already used the layout-independent named key `'Insert'` and is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # programs/server/play.html
…plicating a tab On macOS, Ctrl+Click is the secondary (context-menu) click, not a new-tab gesture. The tab-title click handler treated any Ctrl-modified click as a duplicate gesture, so a macOS Ctrl+Click on a tab title duplicated the tab instead of leaving the gesture to the browser. Gate the duplicate branch the same way as `set_query_or_run`: `e.shiftKey || (isMac ? e.metaKey : e.ctrlKey)`, and ignore plain macOS Ctrl+Click entirely.

In the Web UI (
play.html), middle-clicking a tab's title, or Ctrl/Shift/Cmd+clicking it (the conventional browser gesture for "open in a new tab"), now duplicates the tab. The copy carries over the query and parameters, is inserted right after the source tab, and becomes active.The duplicate's title is the source title with a
" N"suffix, N starting at 1 and incrementing until unique. The full source title is always used as the base — no trailing numeric suffix is stripped — so duplicating"Query A 2"yields"Query A 2 1", and a user-authored numeric suffix is preserved ("Incident 2025"becomes"Incident 2025 1", not"Incident 1"). A title-only heuristic can't reliably distinguish a user's trailing digits from a duplicate counter, so no collapsing is attempted.Middle clicks fire
auxclickrather thanclick, so both events are wired up, and the middle-click autoscroll is suppressed. On Linux/X11 the middle button additionally pastes the PRIMARY selection into the freshly-focused query editor; this is suppressed for exactly that one paste, so no clipboard content is appended after the duplicated query. A normalCtrl+V(orShift+Insert) is unaffected.If a tab's title is being edited inline when a duplicate gesture fires, the pending edit is committed first so the copy is built from the final title rather than a stale value; tab-bar rerenders (including those triggered by query completion) also flush any open title edit.
Note: the PRIMARY-paste guard mirrors the mechanism in #109246. As that PR is not yet merged, this branch includes the same shared guard infrastructure to stay self-contained against
master; whichever of the two merges second will need a small conflict resolution in that block.Related: #109246
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
In the Web UI, middle-clicking a tab's title (or Ctrl/Shift/Cmd+clicking it) now duplicates the tab.
Documentation entry for user-facing changes
🤖 Generated with Claude Code