4345 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 64c8e40f76 the face is Zen, and it ships from @hanzo/font
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.

Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.

Comments naming a face we no longer use are deleted, not reworded.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 10:25:22 -07:00
zeekayandHanzo Dev ff3d14fcff delete the vendored upstream docs tree, and the NonCommercial snippets it fed
docs/ was 502M and 23,841 files: upstream's entire documentation site, in ten
languages, with their branding throughout. Nothing in our pipeline touches it —
the image builds from the root Dockerfile and cmake compiles no documentation,
and our CI is one image build rather than upstream's Praktika jobs, so the doc
jobs and the style check that reads this tree never run for us. docs.hanzo.ai
serves our documentation and 404s /datastore, so none of it reached a reader
either.

The licence is the part worth knowing. docs/LICENSE is CC BY-NC-SA 4.0 —
NONCOMMERCIAL — which is not this repository's Apache-2.0. So that tree was
never ours to ship in a commercial product, independent of whose name is on it.

That exposure did not stop at the tree: programs/server/docs.html inlined four
of those snippets (a JSON-type guide, two settings tables, a data-type mapping)
and that page IS served by the binary. Deleting the tree would have left the
copies behind, so DOC_SNIPPETS is emptied — the resolver still iterates it, so
an entry importing one renders without it rather than failing, and writing our
own is a matter of adding entries back.

The root LICENSE and NOTICE are untouched and still carry the upstream Apache
copyright. The eleven legal files that went with the tree were docs/LICENSE
itself and ten translations of one system-table reference page.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 14:03:51 -07:00
zeekayandHanzo Dev 32c3ef8786 rebuild Lexer.wasm so the pages call exports that exist
play.html's SQL highlighting was dead. The C API had been renamed to
datastore_lexer_* in Lexer.cpp and play.html's calls renamed with it, but the
base64 payload embedded in the page was never rebuilt — it still exported
clickhouse_lexer_*. So play.html asked WebAssembly for six functions the module
does not have. docs.html kept the old call names, which is the only reason its
highlighting still worked.

This is the failure mode worth naming: rename either side of a binary contract
alone and nothing errors loudly — the highlighter just stops highlighting.

Rebuilt from src/Parsers/Lexer.cpp with the flags src/Parsers/CMakeLists.txt
uses (-Os -fno-exceptions -fno-rtti -DLEXER_STANDALONE_BUILD --target=wasm32
-flto -nostdlib -Wl,--no-entry -Wl,--export-all, plus -std=c++20 for the fold
expressions). 6720 bytes, exporting datastore_lexer_{size,create,next_token,
token_is_error,token_is_end,token_is_significant}. Embedded in BOTH pages and
both now call those names — verified by instantiating the payload and checking
every symbol each page uses is present, which is the check that would have
caught this when the rename happened.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 11:56:18 -07:00
zeekayandHanzo Dev e53ccdf6d5 the pages call the server this fork actually is
Three of these were not branding at all — they were live breakage, because the
server was renamed and its consumers were not:

- dashboard.html parsed `ClickHouseAsyncMetrics_` while PrometheusMetricsWriter
  emits `DatastoreAsyncMetrics_`. Paste the Prometheus output into the metrics
  editor and it matched nothing.
- trace-visualizer queried `attribute['clickhouse.thread_id']` while
  OpenTelemetryTraceContext records `datastore.thread_id`. The trace tab found
  no spans.
- both visualizers loaded `js/query-clickhouse.js`; the file on disk is
  `query-datastore.js`, so the script 404'd and the query function never
  existed.

The rest is the name: titles, navbar brands, the status line a user reads after
connecting, links into upstream's repo and docs site, their Cloud badges, and
tests-visualizer pulling a logo from their CDN and querying their public demo
server. Local symbols and DOM ids went with them (queryDatastore,
parseDatastoreTrace, #tabDatastore).

LICENSE, NOTICE and every copyright notice are untouched — no removed line in
this diff carries a ©, an SPDX id or an attribution.

`clickhouse_lexer_*` in docs.html STAYS, and a comment at the embed site says
why: those export names are baked into the base64 WebAssembly payload, so the
JS must match the blob. Renaming means renaming the C API, rebuilding
Lexer.wasm, re-embedding it and updating the calls in one change; rename either
side alone and syntax highlighting fails silently.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 10:17:16 -07:00
zeekayandHanzo Dev 4488b456c5 a link on our own page points at our own source
Apache-2.0 grants no trademark licence, so the upstream name and its links do
not belong on a surface a user of this fork sees. The four links that pointed
at the upstream repository — the source-code link, the two marks beside it,
and the About link in the map's attribution strip — point here.

What stays is not branding: the dashboard parses metric names off the wire,
and that prefix is what this server actually emits. The regex, the comment
describing the format and the example in the placeholder are one identifier,
not a name. Changing them means changing the metric the server publishes,
which is a decision about a wire format and every scrape config already
reading it.

The copyright and the attribution are untouched in NOTICE, which is where a
redistributor is asked to keep them.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-14 09:57:23 -07:00
zeekayandHanzo Dev a7786758f8 Restore upstream citations broken by a global rename
A prior ClickHouse to Datastore rename rewrote `github.com/ClickHouse/ClickHouse`
to `github.com/Datastore/Datastore`, a repository that does not exist. That
turned 162 references across 131 files into dead links, and turned captured
stack traces and historical changelogs into false statements.

A citation of upstream is a fact, not branding. Product surfaces keep the Hanzo
Datastore name; references to the upstream project point at the upstream project.
Also untracks `.claude/`, agent scratch that was never meant to be committed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 19:24:23 -07:00
zeekay 9a90943b8f merge: ClickHouse 26.7.1.1315-stable into datastore — preserve debrand
13,838 upstream commits, forward from the 26.6.1.1 fork point. 129 conflicts:
37 resolved mechanically (take upstream's file, re-apply the brand sweep) and
92 by hand where our fork and upstream had both changed the same code.

The rule throughout: upstream owns the engine, we own the naming and a small set
of deliberate deviations. Never resolve by discarding an upstream change — a
dropped rename or a missed new parameter becomes a compile error a thousand
files away.

Deviations verified intact after the merge:
  ENABLE_XRAY still defaults OFF        (CMakeLists.txt:105)
  X-Datastore-User read by the server   (authenticateUserByHTTP.cpp)
  X-ClickHouse-* in src/                0
  hanzo/research                        present
  system.*_log retention bound          present
  ArrowFlight rip                       held against upstream re-adding it

Two things worth naming, because neither shows up as a conflict:

  src/CMakeLists.txt gained an UNGUARDED
  `add_object_library(datastore_storages_arrowflight Storages/ArrowFlight)` from
  upstream, for a directory our gRPC rip deleted. It sat outside every conflict
  hunk, so a resolver reading only the markers would sail straight past it into
  a CMake configure error. Removed; the Server/ArrowFlight sibling at line 474
  stays because it is inside `if (TARGET ch_contrib::arrow_flight)`.

  The brand sweep had rewritten github.com/ClickHouse/ClickHouse issue links to
  github.com/Datastore/Datastore — a dead host. Those point at upstream's own
  tracker and are restored, alongside the contributors list, sql.clickhouse.com
  and the ClickHouse Inc copyright: attribution is theirs, not ours to rename.

base/glibc-compatibility/memcpy/memcpy.h is deleted, not kept: upstream dropped
it for LLVM-libc's, and our only change to it was a comment rebrand.
2026-07-26 14:47:43 -07:00
Raúl MarínandGitHub 9db0c8259b Merge pull request #109825 from Algunenano/macos-async-signal-safe-unwind
Support the query/memory profiler and trace_log symbolization on macOS
2026-07-21 17:57:25 +00:00
Alexey MilovidovandGitHub be96bb285d Merge pull request #109245 from ClickHouse/play-single-value-full-height
Web UI: show single-value results with full height
2026-07-21 15:10:25 +00:00
Alexey Milovidov ba5fc36dae Merge remote-tracking branch 'origin/master' into play-single-value-full-height
# Conflicts:
#	programs/server/play.html
2026-07-21 06:58:14 +00:00
Alexey MilovidovandClaude Fable 5 327b8eaf00 Merge remote-tracking branch 'origin/master' into web-ui-parse-error-marker
The persistent-tabs work on master rewrote the query execution in
`play.html` into per-tab `postImpl`/`materializeResult` and brought its
own interim mid-stream `{"exception":...}` handling plus a
`compactFailureSnapshot`/`restoreErrorSnapshot` pair for failed
snapshots. Reconciled by porting this PR's failure handling onto the new
architecture:

- The live table branch keeps master's deferred hidden-tab layout
  (`_pendingTranspose`/`_pendingChangeLayout`) and this PR's
  `__exception__` marker detection, framing-break tolerance and
  `display_error`/`exception_tag` in the failure return.
- The raw-format branch gains the PR's full marker/in-band handling,
  adapted to `tab.reqNum` supersede checks.
- `renderFailedSnapshot` (marker- and tag-aware, mirrors the live
  rendering) replaces the interim `restoreErrorSnapshot` in both restore
  paths; `compactFailureSnapshot` is kept in the save path, so an
  oversized failure persists a compact body that now replays as the
  streamed prefix with the persisted `display_error` beneath.
- The restore guards keep master's `data == null` semantics (an empty
  `''` body still renders) while letting a failure with only
  `display_error` restore as its error.

Verified with a node vm harness over the extracted functions
(marker parsing/tag validation/in-band probing/snapshot restore
round-trips, including the compact forms).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 04:55:24 +00:00
Alexey MilovidovandClaude Fable 5 3b90127641 Fix syntax error in play.html: no backticks in a comment inside a template literal
The single-value CSS comment was added inside the shadow-root style block,
which lives in a JS template literal; the backticks in the comment terminated
the literal and broke the page script. Found by test_play_reconcile_startup:
https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=109245&sha=03660d963d8d083e14c4df7229909556e7004a85&name_0=PR

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 19:13:25 +00:00
Alexey MilovidovandClaude Fable 5 20888060bf Web UI: make run identities collision-free across page instances
The run-identity counter (`run_seq`) could still alias an old mid-run
history entry after a reload: a higher `run_id` may survive only in a
deeper Back/Forward entry that never reached `IndexedDB` (the deferred
`persist` window, or `IndexedDB` unavailable), and session-history
entries are not enumerable at startup, so no counter re-seeding can
promise uniqueness against them.

Instead of re-seeding, qualify every id with `run_epoch` — a random
token drawn once per page instance. Ids minted by different page
instances can never be equal, so a post-reload launch can never adopt
the identity of an entry written before the reload, while ids persisted
together by a previous instance (a tab snapshot and the history entries
of the same run) still match each other after a reload. The counter
persistence (`meta.runSeq`) and the startup re-seeding are removed as
obsolete; the harness sandbox gains `crypto` (webcrypto) for the
epoch's `getRandomValues`.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 12:32:27 +00:00
Alexey Milovidov c29d42340f Merge remote-tracking branch 'origin/master' into play-single-value-full-height 2026-07-20 08:14:41 +00:00
Alexey MilovidovandClaude Fable 5 1051750195 Web UI: survive reloads with the run-identity counter
The `run_seq` counter backing per-run identities (`tab.launchRunId`,
`tab.result.run_id`, history entries' `state.run_id`) was page-local while
the ids it minted are persisted in browser history and `IndexedDB` tab
snapshots. After a reload the counter restarted at `1`, so a fresh launch
could reuse the identity of an old mid-run (`result: null`) history entry
and `window.onpopstate` would keep the newer result under the older URL.

`persist` now stores the counter in the meta record (like `tabSeq` /
`tabTitleSeq`) and `reconcileStartup` re-seeds `run_seq` above every
persisted `run_id` it can see: the restored meta counter, the saved tab
snapshots (covers meta records from before the counter was persisted), and
the current `history.state` (covers a reload whose `IndexedDB` state is
gone while the history entry survived).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-20 03:02:04 +00:00
Alexey Milovidov a4964713e0 Web UI: identify the mid-run history entry by a real run id
`window.onpopstate`'s `result: null` shortcut (which lets a tab's live state
stand in for a mid-run history entry so a completed background run is not blanked
by Back/Forward) inferred "same run" from matching `query` + `params` against
`tab.result.query`. That misidentified two real cases:

- A multi-statement `Run selected` stores only the selected statement in
  `tab.result.query`, while the mid-run `result: null` entry keeps the full editor
  text in `event.state.query`, so `Back` to that entry still reopened the tab blank.
- A later rerun with the same text and bindings satisfied the predicate too, so an
  unrelated later result could be shown under an older entry's URL.

Give each run launch a unique identity (`run_seq` -> `tab.launchRunId`), stamp it
onto the completed snapshot (`tab.result.run_id`) and every history entry
(`state.run_id`), and gate the shortcut on the id instead of the query text: the
live state stands in only when the tab's still-running launch or completed snapshot
carries the entry's own `run_id`. Legacy entries persisted before `run_id` existed
fall back to the earlier launch-query / snapshot-query heuristic.

Addresses review: https://github.com/ClickHouse/ClickHouse/pull/109425#discussion_r3609616320
2026-07-19 20:07:44 +00:00
Alexey Milovidov ed746fa5d1 Merge remote-tracking branch 'origin/master' into web-ui-persistent-tabs
# Conflicts:
#	programs/server/play.html
2026-07-19 20:04:07 +00:00
Alexey MilovidovandGitHub 4ebc4b62e6 Merge pull request #109419 from ClickHouse/web-ui-docs-link-user
Web UI: add user name to the Documentation link URL
2026-07-19 19:03:02 +02:00
Shaun StruwigandGitHub 01a6dc88ec Merge pull request #110949 from Blargian/codex/fix-external-doc-links
Docs: update historical broken external documentation links
2026-07-19 16:04:33 +00:00
Alexey MilovidovandGitHub 4a006cc8e9 Merge pull request #109439 from ClickHouse/web-ui-pinned-columns
Web UI: add pinned columns
2026-07-19 15:24:21 +00:00
Shaun Struwig 9fe065fe21 Sync embedded Avro documentation snippets 2026-07-19 12:57:41 +02:00
Alexey MilovidovandGitHub 78bb60b4cf Merge pull request #110705 from ClickHouse/web-ui-terminal-icon-comment-followup
Web UI: fix the last two stale new-tab gesture comments in play.html
2026-07-19 04:55:32 +00:00
Alexey MilovidovandClaude Opus 4.8 7d93284240 Web UI: only keep a live result for a null history entry when it is the same run
`onpopstate` treats a result-less history entry specially: an entry written while
its tab was mid-run holds `result: null` (a run nulls `tab.result` at launch and
reinstalls it only on completion, and a background run finishing in another tab
reinstalls the result in memory without rewriting the null entry). Rather than
time-travelling over such an entry and blanking a tab that is still streaming or
just finished, the handler navigated to it like a plain tab switch when the tab
was in flight or already held a result.

That shortcut was too broad: it preserved ANY later `tab.result`, not
specifically the run the null entry came from. Start `Q1` in a tab, switch away
(entry stores `result: null`), later run `Q2` in the same tab, then jump straight
back to the old mid-run entry via the browser history menu / `history.go`:
`tab.result` is now `Q2`, so the shortcut kept `Q2` on screen even though the
entry predates that run.

Gate the shortcut on run provenance: a mid-run entry records the launch query
(and params), and that identity survives on the tab as the still-running launch
(`launchQuery`) or the completed in-memory snapshot (`tab.result`). Keep the live
state only when it matches the entry's query (and, for a completed result, its
params); otherwise fall through to plain time-travel and render the entry itself.
This preserves the legitimate "background run finished after a tab switch" case
while dropping an unrelated later result.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-19 01:44:24 +00:00
Alexey MilovidovandClaude Opus 4.8 8bf50decea Web UI: don't restamp the popped history entry during onpopstate connection replay
The persistent-tabs `onpopstate` restores a navigated-to entry's connection by
assigning `url_elem`/`user_elem` and dispatching synthetic `input` events so the
normal connection listeners (`checkURL`, `updateDocsURL`, `resetCredentialsStatus`,
`resetConnectionScopedState`) replay a real connection edit's side effects.

But `resetConnectionScopedState` also restamps the CURRENT history entry via
`refreshCurrentHistoryEntry(getActiveTab())` whenever the connection or selection
changed. Inside `onpopstate` that is wrong twice over: `history.state` is already
the popped entry the restore is rebuilding, and `activeTabId` still points at the
tab being left (it is rebound only later in the handler). So for a same-tab
Back/Forward that changes the connection with a non-null `selected_database`, the
synthetic `url` input cleared the selection and then rewrote the popped entry from
the leaving tab's newer query/cleared `database=`, corrupting the time-travel
target before the handler could restore `event.state`.

Guard the synthetic replay with `restoring_connection_from_history` (set only
around the dispatch, cleared in a `finally`); while it is set,
`resetConnectionScopedState` still resets its connection-scoped caches but skips
the history restamp. `onpopstate` performs the authoritative entry/URL restore
itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-19 01:42:39 +00:00
Alexey Milovidov f7d9ace6b3 Address review: reassert URL display state for queryless shared links
The startup reassertion of `pinned_columns` / `color_modes` in
`reconcileStartup` was gated on the URL hash carrying a non-empty query.
But `writeHistoryEntry` emits a queryless display-state link (e.g.
`?pinned_columns={...}#`) once the user clears the editor while columns
stay pinned, and such a link is still authoritative for the display state.

With the old gate, opening a saved workspace and then loading such a
queryless link let `activateTab` restore the saved tab's set, skipped the
reassertion block (because the query was empty), and the trailing
`syncHistory` re-stamped the URL without the link's pins/colors — losing
the advertised "reload/share preserves it" state for queryless links.

Gate the block on display-state authoritativeness instead: run it when the
URL carries a query OR a well-formed `pinned_columns` / `color_modes`
param. A malformed-only param does not establish authority (left
non-destructive per the `/* ignore malformed */` contract), and a URL with
neither a query nor a well-formed display param is a plain reload whose
saved display state must be preserved. Also exclude a blank stale-reload
echo (`stale_blank_reload_bare`), matching the parameter-reconciliation
gate above.

Addresses the `clickhouse-gh` review at programs/server/play.html:10503.
2026-07-19 01:19:59 +00:00
Alexey Milovidov 38c2aad3bf Merge remote-tracking branch 'origin/master' into web-ui-docs-link-user 2026-07-19 00:22:25 +00:00
Shaun StruwigandGitHub fba2326ecb Merge pull request #110195 from ClickHouse/engine-docs-autogen
Autogenerate component reference documentation from in-code structured docs
2026-07-18 23:39:03 +00:00
Alexey Milovidov 485308c553 Merge remote-tracking branch 'origin/master' into web-ui-terminal-icon-comment-followup 2026-07-18 21:40:15 +00:00
Alexey Milovidov c8b5402ef0 Merge branch 'master' into web-ui-persistent-tabs
Reconcile the persistent-per-tab result panels with master's newly merged
`web-ui-selected-database-default` feature, which reworked the same
`programs/server/play.html` run/history/download/onpopstate paths (selected
database threaded through the request, history URL and download; connection
provenance recorded per result snapshot; `run=1` dropped when the live
connection/database diverges from the producing one; `deferred_run_cancelled`).

Key resolutions (15 conflict hunks + threading):
- `postImpl`/`postSingle`/`postMulti` now take BOTH the PR's `tab` (run
  ownership, before-await connection/param snapshot) AND master's
  `query_database` + `connection` tuple, unified on the `{ url, user, password }`
  shape. The database and connection are snapshotted in `postOne`/`postAll`
  BEFORE the lexer preflight await and threaded through, extending the PR's
  race-fix (a mid-await tab switch + database/connection edit cannot retarget an
  already-launched run) to the database master added.
- Dropped the removed global `last_query_start` (the PR uses per-run
  `query_start`); kept master's `deferred_run_cancelled`.
- Saved result snapshots carry `database`/`url`/`user`; download provenance is
  recorded per tab (`tab.downloadDatabase`/`downloadUrl`/`downloadUser`) in
  `postSingle` and `materializeResult`, mirrored into the shared
  `last_*_for_download` by `paintDownloadCopy`, so switching tabs and downloading
  reproduces the active tab's own result against its producing connection.
  `materializeResult` hides Download (fail closed) for a legacy snapshot whose
  producing connection is unknown, matching master's `download_reproducible` gate.
- `writeHistoryEntry` keeps the PR's materialized-tab `color_modes` handling and
  uses master's `carries_run`/`entry_user`/`entry_server`/`entry_database`.
- `restoreEditor` stays editor-only (the result and its download targets are
  restored per tab by `materializeResult`).
- `window.onpopstate` keeps BOTH the PR's connection restore + `entry_has_result`
  live-tab guard AND master's connection-gated selected-database restore; updated
  the now-stale "connection is not restored" comment.

Validated with `node --check` on the extracted `<script>`.
2026-07-18 21:33:56 +00:00
Alexey MilovidovandClaude Opus 4.8 d1747d6812 Address review: carry saved pins/colors before dropping a substituted tab's snapshot
A fresh/shared link that reuses an existing tab name but carries a different
query (`?tab=Name&pinned_columns=not-json#<other query>`) hit a destructive
edge in startup reconciliation. When `reconcileStartup` substitutes the URL
query into the matched tab it clears `match.result`; a run-backed tab keeps its
`pinned_columns` / `color_modes` only inside that result snapshot, so once it is
null `restoreFromHistory` (which reads `state.result || state`) restored an
empty set. The malformed-URL-param guard then "preserved" that already-emptied
state and the trailing `captureActiveTab` / `persist` wrote the wipe into
`IndexedDB`, contradicting the parse-time `/* ignore malformed */` contract.

Carry the tab's saved display state up to the tab top level (exactly where a
result-less tab already keeps it, per `activateTab` / `writeHistoryEntry`)
before dropping the snapshot, so activation and the reconcile recover the saved
set. The malformed case now leaves the persisted state untouched; the valid and
absent cases are unaffected (the reconcile overrides the carried value with the
link's set, or clears it for an authoritative-empty link).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 20:11:20 +00:00
Alexey Milovidov 8c4ddaffde Merge remote-tracking branch 'origin/master' into web-ui-pinned-columns
# Conflicts:
#	programs/server/play.html
2026-07-18 20:08:50 +00:00
Alexey Milovidov b4b4953cfe Web UI: fix stale duplicateTab helper comment overstating macOS Ctrl+click
The `duplicateTab` helper comment in `programs/server/play.html` said the tab
duplication is triggered by a middle-click or `Ctrl/Shift/Cmd+click` on the
title. But the actual title-click handler bails out on macOS `Ctrl+click`
(the secondary/context-menu click, left to the browser) and only duplicates on
`Shift+click` or the platform's new-tab modifier click (Cmd+click on macOS,
Ctrl+click elsewhere). Reword the comment to the same platform-accurate phrasing
used elsewhere in the file, so it no longer lumps macOS `Ctrl+click` into the
new-tab/duplicate bucket. Comment-only change, no behavior change.

Addresses: https://github.com/ClickHouse/ClickHouse/pull/110705#discussion_r3608479890
2026-07-18 16:16:47 +00:00
Alexey MilovidovandClaude Opus 4.8 c2413d481b Address review: re-layout all rows when a pinned column widens mid-stream
The incremental streaming path `_pinStreamedRows` recomputed the pinned-column
sticky offsets from the latest header widths but reapplied them only to the rows
each chunk had just appended. With two or more pinned columns, if a pinned column
widened in a later chunk (a wider value arrived) the cumulative `left` / `right`
offsets of the pinned columns after it shifted, so the rows rendered by earlier
chunks kept the old offsets and drifted out of alignment - overlapping or leaving
gaps - until the EOF `applyPinnedColumns` pass corrected everything.

Track the offset vector last applied (`_pinnedLayoutKey`, set by
`_layoutPinnedColumns`) and, on each streamed chunk, compare it against the
offsets computed from the current header widths (`_computePinnedOffsets`, factored
out of `_layoutPinnedColumns`). When they match, still lay out only the new rows,
so the common no-drift case stays proportional to the chunk; when a pinned column
widened, re-lay-out every pinnable row so the earlier rows stay aligned. A change
to the pinned set itself always goes through `togglePin`, which already does a full
`applyPinnedColumns` pass, so the offset signature only ever has to catch width
drift within a fixed pinned set.

This addresses the remaining AI-review concern on
https://github.com/ClickHouse/ClickHouse/pull/109439 (finding at
programs/server/play.html _pinStreamedRows / _layoutPinnedColumns).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 15:50:14 +00:00
Alexey MilovidovandClaude Opus 4.8 785dec6cdb Address review: treat a present-but-empty URL param as malformed, not authoritative-empty
The startup guards for `pinned_columns` / `color_modes` restored from the URL
used truthiness (`if (pinned_columns_from_url)`), but `URLSearchParams.get`
returns `''` for a PRESENT-but-empty param (`?pinned_columns=&color_modes=#...`)
and `null` only for a truly absent one. The empty string is falsy, so the parse
path was skipped entirely: the `*_url_malformed` flags stayed `false`, the parsed
values stayed `null`, and the startup reconciliation block still treated the
params as an authoritative empty set and cleared the matched saved tab's pins /
colors into browser history and `IndexedDB`.

That reintroduces the "present but malformed must not wipe persisted state" bug
for the empty-string form of the same input. Use a presence check
(`!== null`) so an empty string enters the parse path, `JSON.parse('')` throws,
the malformed flag trips, and the persisted state is left untouched. The app
itself never emits an empty `pinned_columns=` / `color_modes=` param
(`persistPinnedColumns` / `persistColorModes` / `buildHistoryParams` set it only
when non-empty and delete/omit it otherwise), so an absent param remains the
authoritative "no pins / no colors" state.

Validated by code tracing and `node --check` on the extracted script.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 12:37:18 +00:00
Alexey MilovidovandClaude Opus 4.8 c5bc84465b Web UI: replay connection-edit side effects on Back/Forward restore
`window.onpopstate` restored a history entry's `url`/`user` by assigning
`.value` directly and only calling `resetConnectionScopedState`. Direct
assignment fires none of the connection inputs' `input` listeners, so a
Back/Forward across a connection change left the previous connection's
password and its `ok`/`fail` badge attached to a different restored
server/user, never re-pinged the new host (`checkURL`), and left the
Documentation link pointing at the previous server. The page could then
display connection `A` while still advertising `B`'s status and
submitting `B`'s password on the next run.

Replay the same side effects a real connection edit performs: clear
`password_elem` (the authenticating password is deliberately never
persisted to history, so it is not recoverable here), then dispatch
synthetic `input` events on the connection inputs so every registered
listener (`checkURL`, `updateDocsURL`, `resetCredentialsStatus` and
`resetConnectionScopedState`) runs exactly as for a typed edit. The
synthetic events carry `isTrusted === false`, so `markBootstrapDirty`
ignores them; the navigation already marked the workspace dirty.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 09:45:24 +00:00
Alexey MilovidovandClaude Opus 4.8 2be10633b1 Address review: don't wipe persisted pins/colors on a malformed URL param
A shared/bookmarked link that carried a malformed `pinned_columns` or
`color_modes` param (e.g. `?pinned_columns=not-json`, or a value that
decodes to a JSON primitive/array rather than an object) was treated by
startup reconciliation exactly like an authoritative empty state: both
`pinned_columns` and `column_color_modes` were reset to `{}`, the empty
state was mirrored onto the matched saved tab, and the trailing
`syncHistory` / `scheduleSave` persisted the wipe into browser history
and `IndexedDB`.

That contradicts the parse-time `/* ignore malformed */` contract, which
was meant to leave the state untouched on a decode failure: `url_*` being
`null` alone cannot tell "param absent" (clear) from "param present but
malformed" (skip), and `nullProtoMap` of a non-object returns a truthy
empty map that is mistaken for an authoritative "no pins / no colors" set.

Track param presence separately from a successful decode with the new
`pinned_columns_url_malformed` / `color_modes_url_malformed` flags, set
whenever the param is present but does not parse into an object. During
reconciliation, reassert each field independently: a present-and-decoded
param sets it, an absent param clears it to empty, and a present-but-
malformed param leaves the restored saved-tab value (and its persisted
snapshot) untouched. The subsequent `syncHistory` then re-stamps the URL
with the preserved value, cleaning the malformed param out without data
loss.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 09:45:23 +00:00
Alexey MilovidovandClaude Opus 4.8 c85a325d6d Address review: fix pinned-column stacking and mid-stream pin availability
Two findings from `clickhouse-gh` on the pinned-columns feature.

1. Stacking order: a `td.pinned-column` had `z-index: 5`, below the
   `.td-selected` layer (`z-index: 10`). Selecting an unpinned cell and
   scrolling it under a pinned column made the selected cell paint over
   the pinned column, hiding it and breaking the "pinned columns stay
   visible while the rest scrolls underneath" contract. Raise the pinned
   body layer to `z-index: 11` (above ordinary selected cells) and the
   pinned-and-selected case to `z-index: 12` (one step higher, so its
   expanded content still paints over neighbouring pinned cells). Both
   stay well below the header band (`100 +`), so headers still win.

2. Mid-stream pin availability: `_pinStreamedRows` returned early unless
   a column was already pinned, so for an ordinary wide streamed result
   `_updatePinAvailability` never ran during streaming and the pin
   toggles stayed disabled/hidden until the EOF `applyPinnedColumns`
   pass. A user could not pin a column on a long-running wide query
   before it finished. Now, when nothing is pinned, refresh only the
   horizontal-scroll availability (O(columns), no per-row reflow) after
   each flushed chunk, so the toggles become usable mid-stream; the
   already-pinned path is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 07:26:08 +00:00
Alexey MilovidovandClaude Opus 4.8 921e6a5c50 Address review: stop the two guard comments overstating macOS Ctrl+click as a new-tab open
The `databases_toggle` click guard (`play.html:4821`) and the terminal
`wantsNewTab` predicate / `terminalIcon` click guard both return on any
`e.ctrlKey || e.metaKey || e.shiftKey`, so on macOS they also fire for
`Ctrl+click` - which is the secondary/context-menu click, not a new-tab
gesture. The comments described every guarded modifier click as
"open in a new tab or window", overstating the native-anchor modifiers this
PR set out to correct. Reword the three comments to be neutral and accurate:
they defer any modified click to the browser's native link handling - a new
tab or window for the new-tab gestures, and the context menu for macOS
`Ctrl+click`. Comment-only change, no behavior change.

Addresses: https://github.com/ClickHouse/ClickHouse/pull/110705#discussion_r3600348849

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 07:17:39 +00:00
Alexey Milovidov 46fac34f42 Web UI: restore single-query image results without misclassifying them as raw
A single-query image result (e.g. `FORMAT PNG`) streams as a blob that
`postImpl` renders directly, leaving `reply` empty, so `postSingle` persisted
the snapshot as `data: ''`. On restore, `materializeResult` treated the empty
string as a non-null, non-table payload and fell into the raw branch, reopening
the tab as an empty raw text result with the `Copy` button enabled instead of as
an image result (after reload, `Back`/`Forward`, or first activation from
`IndexedDB`).

Store `data: null` for image results, exactly like an oversized result and like
the per-statement multi-query path in `postMulti`. The existing
`data == null` guard in `materializeResult` then reopens the tab idle instead of
rendering empty raw text, and the run stays reproducible from the `run=1` URL
(images are already grouped with oversized results as "not snapshotted" in
`buildHistoryParams`).

Fixes an AI-review finding on the current head.
2026-07-18 05:27:21 +00:00
Alexey Milovidov 210876942b Web UI: keep pinned columns sticky while a table result is streaming
`applyColumnColors` (which lays out the pinned columns via
`applyPinnedColumns`) is only reached in `postImpl` after the streaming
loop has drained the whole response. A wide result restored from a shared
`?pinned_columns=...&run=1` link therefore rendered every row unpinned for
the entire streaming phase, and the pin toggles stayed disabled/hidden,
because `_updatePinAvailability` also ran only from that terminal pass. The
pinned layout snapped into place only once EOF arrived, violating the
"pinned columns stay visible while the table is scrolled" contract for
long-running streamed queries.

Extract the per-column layout core into `_layoutPinnedColumns`, shared by
`applyPinnedColumns` (all rows) and a new `_pinStreamedRows` that the
streaming loop calls after each `flushFragment`. `_pinStreamedRows` lays
out only the rows the chunk just appended (plus the header), so the
per-chunk cost stays proportional to the new rows rather than re-sweeping
the whole table. It is a no-op unless a column is pinned, so an ordinary
streamed result keeps deferring all layout to EOF and pays no per-chunk
reflow. The final `applyPinnedColumns` pass still re-lays out every row
with the final column widths, correcting any offset drift from columns
that widened mid-stream.

Addresses the `clickhouse-gh` AI review finding at `programs/server/play.html`.
2026-07-18 03:58:34 +00:00
Alexey Milovidov ce2db37075 Merge remote-tracking branch 'origin/master' into web-ui-pinned-columns
# Conflicts:
#	programs/server/play.html
2026-07-18 03:57:34 +00:00
Alexey Milovidov 9f26bf43b8 Merge remote-tracking branch 'origin/master' into web-ui-selected-database-default
# Conflicts:
#	programs/server/play.html
2026-07-18 02:25:53 +00:00
Alexey MilovidovandClaude Opus 4.8 06a0f0a291 Web UI: treat Back/Forward during startup as bootstrap-dirty
`window.onpopstate` restored the popped tab/result but did not stop the
`reconcileStartup` reconciliation still running from the page-load URL.
That startup state (`current_url`, `url_query`, `url_tab_name`,
`run_immediately`, `url_color_modes`) is snapshotted once at load, so a
Back/Forward landing before reconciliation finishes its `loadFromDb` /
`activateTab` / `updateQueryParams` awaits could still switch back to the
original tab, re-apply the original URL params and colors, and fire the
original deferred `run=1` on top of the entry the user just navigated to.

Mark the workspace dirty in `onpopstate` — the same `bootstrap_dirty`
treatment tab switches, closes and connection edits already use — so every
remaining `!bootstrap_dirty`-gated branch of reconciliation (the
dirty-startup merge, the URL parameter restore and the auto-run) yields to
the navigation. A genuine Back/Forward is a trusted event so it is not
filtered as synthetic, and once reconciliation has settled the call is a
no-op via `bootstrap_settled`.

Addresses the bot review finding at programs/server/play.html.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-18 02:00:50 +00:00
Alexey Milovidov 444543c8ad Merge remote-tracking branch 'origin/master' into web-ui-persistent-tabs
# Conflicts:
#	programs/server/play.html
2026-07-18 01:59:17 +00:00
Alexey Milovidov 4709a97823 Merge remote-tracking branch 'origin/master' into web-ui-terminal-icon-comment-followup 2026-07-18 01:17:14 +00:00
Alexey Milovidov 4147ca0a48 Merge remote-tracking branch 'origin/master' into engine-docs-autogen 2026-07-17 23:20:05 +00:00
Alexey MilovidovandGitHub 4ab7bb0dcc Merge pull request #109529 from ClickHouse/web-ui-no-restore-empty-tabs
Web UI: do not restore tabs with empty queries on page load
2026-07-17 22:22:54 +00:00
Alexey MilovidovandGitHub 97039a94a7 Merge pull request #110397 from ClickHouse/web-ui-fix-header-title-clipping
Web UI: fix column header titles clipped by a fraction of a pixel
2026-07-17 22:22:02 +00:00
Alexey Milovidov 77b3585680 Render the plan-gating badges' message on the built-in help surfaces
A prop-bearing badge such as `<ScalePlanFeatureBadge feature="S3 Role-Based Access" />` (used by
the `S3Queue` page) collapsed to just its name-derived label on both help surfaces, while on the
website the component renders a substantive message from its attributes
(`docs/snippets/components/ScalePlanFeatureBadge`): which plan the feature requires and how to get
it. Add an identical `badgePayload` to the terminal `help` renderer
(`TerminalMarkdownRenderer.cpp`) and the `/docs` web UI (`docs.html`), mirroring `badgeLabel`:
`ScalePlanFeatureBadge` and `EnterprisePlanFeatureBadge` now render their message (built from the
`feature`, `support`, and `linking_verb_are` attributes exactly as the website components do)
after the badge label, and both get the website's exact label casing as known badges.

New gtest `PlanFeatureBadgeRendersItsMessage` covers the attribute combinations; new stateless
tests drive both surfaces end to end: `04606` asserts `help S3Queue` renders the message through
the real render path, and `04607` asserts the `/docs` page ships `badgePayload` and the message
text.

Addresses the review finding in
https://github.com/ClickHouse/ClickHouse/pull/110195#discussion_r3603607579
2026-07-17 19:05:56 +00:00
Alexey Milovidov 2d0e0287c3 Web UI: cancel the deferred startup run when the current entry cannot be rewritten
Two review findings on the deferred `run=1` startup auto-run, both in the
window before `reconcileStartup` has rebound the current history entry:

- `history.state` survives a reload, so during startup the current entry can
  still belong to a tab of the previous page instance while the active tab is
  only the fresh bootstrap tab. A trusted mutation in that window (a database
  panel click, a connection edit) reached `refreshCurrentHistoryEntry`'s
  `cur.tabId !== tab.id` bail and never got to `writeHistoryEntry`, leaving
  `deferred_run_cancelled` unset — the deferred `postAll` then executed the
  shared query against the newly selected database or edited connection the
  URL never described. The bail now applies the same `carries_run` rule as
  the entry write (via the extracted `liveDivergedFromRun` helper), so the
  cancellation is not lost with the write.

- An entry that exists but carries no `connectionServer` stamp (written by a
  play.html predating the stamp) made both `connection_diverged` and
  `bootstrap_connection_diverged` stay false, so a trusted `url`/`user` edit
  during startup never cancelled the run. An unstamped entry is now treated
  the same as "no authoritative connection stamp yet": the bootstrap term
  falls back to comparing against `initial_connection` for it too.
2026-07-17 17:31:28 +00:00