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]>
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]>
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]>
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]>
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]>
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.
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]>
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]>
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]>
`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
`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]>
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]>
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.
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>`.
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]>
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
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]>
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]>
`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]>
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]>
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]>
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]>
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.
`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`.
`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]>
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
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.