Skip to content

feat(results): progressive loading — fix Running…null, add Cancel (Esc + KILL QUERY)#15

Merged
BorisTyshkevich merged 2 commits into
mainfrom
feat/progressive-loading-cancel
Jun 22, 2026
Merged

feat(results): progressive loading — fix Running…null, add Cancel (Esc + KILL QUERY)#15
BorisTyshkevich merged 2 commits into
mainfrom
feat/progressive-loading-cancel

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

Better visualization of slow/long-query loading, aligned to the design handoff §6b-running. Repro for all three: select sleep(3).

1. "Running…null" bug

The Run button rendered Running…null. setRunBtn called replaceChildren(Icon.play(), span, running ? null : kbd) — while running the third arg is null, and replaceChildren (unlike the h() helper, which skips null children) coerces null into a "null" text node. Fixed by filtering the array before replaceChildren.

2. Cancel a long query

  • Cancel button in the results toolbar (replaces Copy/Export while running, red on hover) + a global Esc handler.
  • Cancel aborts the stream AND issues KILL QUERY WHERE query_id=… (a query_id is now tagged on the run request) so ClickHouse stops server-side work too. Best-effort — swallows errors (e.g. missing privilege) so cancel never throws.
  • The Run button is simply a disabled "Running…".
  • On cancel the partial rows are kept and the result is flagged cancelled (not an error) → a red "Cancelled · partial" badge, with Copy/Export re-enabled.

3. Progressive loading visuals

  • Results toolbar while running: live accent counters — elapsed ms (ticking off a 100ms interval, so it advances even for row-less queries like sleep), rows read, bytes scanned.
  • Body: a 2px streaming strip (determinate fill at read/total, else an indeterminate sweep) and a "Starting query…" spinner before the first batch. Partial rows stream into the table as they arrive (no blocking spinner).

Files

core/stream.js (cancelled flag) · net/ch-client.js (query_id + killQuery) · ui/app.js (null fix, run() query_id/ms-tick/cancelled, cancel()) · ui/shortcuts.js (Esc→cancel) · ui/results.js (running toolbar, strip, badge) · ui/icons.js + styles.css (spinner + animations).

Tests & verification

npm test green, per-file gate held (stream/ch-client/icons/shortcuts at 100; results branches ≥90; app.js ≥95/≥90). New tests: setRunBtn no-null, cancel() abort+KILL, tickElapsed, killQuery SQL, Esc→cancel, streaming/"Starting query…"/cancelled-badge render.

Verified live on otel (select sleep(3)): button reads "Running…" (no null); toolbar shows ticking ms + spinner + rows/bytes + Cancel · Esc; sweep strip; Esc cancels mid-run → "Cancelled · partial", Copy/Export back. Deployed to otel + antalya.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef

Isolator acm and others added 2 commits June 22, 2026 10:11
…Esc + KILL QUERY)

Three fixes to how a slow/long query (repro: `select sleep(3)`) is shown,
aligned to the design handoff §6b-running.

1. "Running…null" bug: setRunBtn passed `null` as a replaceChildren arg while
   running, and replaceChildren coerces null → a "null" text node (unlike the
   h() helper which skips it). Filter the array before replaceChildren.

2. Cancel a long query: a Cancel button in the results toolbar (replaces
   Copy/Export while running, red on hover) plus a global Esc handler. Cancel
   aborts the stream AND issues `KILL QUERY WHERE query_id=…` so the server
   stops too (best-effort; swallows errors). The Run button is just a disabled
   "Running…". On cancel the partial rows are kept and the result is flagged
   cancelled (not an error) → a red "Cancelled · partial" badge, Copy/Export
   re-enabled.

3. Progressive loading visuals: while running the results toolbar shows live
   accent counters — elapsed ms (ticking off a 100ms interval, so it advances
   even for row-less queries like sleep), rows-read, bytes-scanned — and the
   body shows a 2px streaming strip (determinate fill at read/total, else an
   indeterminate sweep) with a "Starting query…" spinner before the first
   batch. Partial rows stream into the table as they arrive.

- core/stream.js: newResult adds `cancelled`.
- net/ch-client.js: query_id on the run URL; killQuery().
- ui/app.js: setRunBtn null fix, run() (query_id + ms tick + cancelled), cancel().
- ui/shortcuts.js: Esc → cancel while running.
- ui/results.js: running toolbar (live counters + Cancel), streaming strip,
  "Starting query…", cancelled badge.
- ui/icons.js, styles.css: spinner icon + spin/runsweep animations and states.

Tests cover all of it (gate green); verified live on otel with select sleep(3):
button reads "Running…" with no null, live counters + Cancel + sweep strip
appear, and Esc cancels → "Cancelled · partial".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
…ws ~16s → ~0.5s)

The streaming Table query was sent with wait_end_of_query=1, which buffers the
entire response server-side and sends it only after the query finishes — so
"progressive" loading wasn't progressive: on SELECT * FROM a 1.3M-row table the
first rows appeared at ~16s. Measured against the same query with the flag off,
the first block arrives at ~0.5s (~600 rows).

Keep wait_end_of_query=1 only for the raw (TSV/JSON) modes — those read the whole
body anyway, so they lose nothing and keep a clean HTTP error status. The
streaming path drops it and already surfaces mid-stream errors via the in-band
`exception` line (applyStreamLine), so error handling is unchanged.

Verified live on otel: SELECT * FROM claude_otel.otel_logs now paints its first
~630 rows at ~473ms (was ~13–16s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QennTvGKAtJZrv9EpQagef
@BorisTyshkevich BorisTyshkevich merged commit 6e5fd79 into main Jun 22, 2026
2 checks passed
@BorisTyshkevich BorisTyshkevich deleted the feat/progressive-loading-cancel branch June 22, 2026 16:43
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.

1 participant