Repository navigation
fix(client): request a scalar target on its own and track the tier a session has reached (rows-first c5b) - #1036
Closed
paddymul wants to merge 20 commits into
Conversation
…protocol (rows-first c0a) Tests for the client half of plan 1 phase 0a. A server that sends the summary stats separately, or not at all, leaves the first message without values the client has been assuming. BuckarooView: a change that reached the model before the effect subscribed, one initial_state with metadata decoding once, and out-of-order decodes applying the newer. Pinned rows: a valueless key shows a placeholder with its own row id while df_meta.stats.status is pending, and is omitted when not_computed. The simple tooltip returns nothing for a valueless cell. color_map is silent without bins and restyles when they arrive. An unrelated df_data_dict update keeps the in-flight indicator when the server reports df_meta.stats. One Storybook Playwright test covers pending, not_computed and complete in a real browser, and is added to the Storybook list in scripts/test_playwright_storybook.sh. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… c0a) BuckarooView reads model.get() for every key once its listeners are in place, so a change:* emitted before the effect ran reaches React. All df_data_dict values go through one decoder (makeLatestDictDecoder) that skips a dict it has already seen and applies only the newest decode, so an initial_state with metadata decodes once and out-of-order decodes cannot overwrite a newer one. standalone.tsx gets the same decoder and catch-up. df_meta.stats.status reaches the grid as stats_status. While pending, a required pinned key with no value becomes a placeholder row that carries its key as index (unique row id, empty value cells). When not_computed it is omitted. A missing df_meta.stats, or any other status, keeps the old behaviour. The simple tooltip returns nothing for a cell with no value or no row data. color_map no longer logs when bins are missing, and the grid refreshes the color-mapped columns when their bins change after the first render. That needs RenderApiModule, which was not registered, so api.refreshCells logged AG Grid error 200 and did nothing. BuckarooInfiniteWidget keeps inFlight when only df_data_dict changed and the server reports df_meta.stats; the answer to the dispatched change is a frame with a new df_meta. Without df_meta.stats the rule is unchanged. The Playwright story test no longer hovers a valueless pinned cell: with the old tooltip it still passed, since AG Grid does not call the tooltip for an empty value. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Jest tests, driven through a WebSocketModel with a fake socket, for the client half of the stats wire: a stats_update is key-merged into all_stats and dropped when its stats_gen is not the expected one, the expected gen follows df_meta.stats.gen on every applied initial_state, a merge in flight is discarded or redone when a frame replaces the dict, and stats_aborted moves the status. Plus withStatsCapability and BuckarooServerView putting ?caps=stats_update on the WebSocket URL, and a server Playwright test that the standalone page does the same. StatsChannel.ts is a stub (identity withStatsCapability) so the tests fail on assertions rather than on a missing module. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ct (rows-first c2) Three more cases for the stats_update merge, found untested after the first test commit: a wide parquet_b64 payload as the server sends it (the shared summary_stats fixture, not a json envelope), a model that holds no df_data_dict yet, and a dict with no all_stats key. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Two stats_update messages for one gen are applied in arrival order even when the first payload decodes more slowly: the final update merges last and the status completes only after both merges. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ability (rows-first c2) StatsChannel is the client half of the stats wire. WebSocketModel hands it every text frame first. A stats_update for the gen the model's df_meta reports is decoded and key-merged into df_data_dict.all_stats (new row objects, a new dict), updates apply one at a time in arrival order, and a final update sets df_meta.stats to complete at the update's tier. A merge is dropped when the gen moves on while it decodes and redone when a frame replaces the dict. stats_aborted for the expected gen sets error or not_computed. Other message types are ignored as before. withStatsCapability adds caps=stats_update to a WebSocket URL; both wiring copies (BuckarooServerView and the standalone page) use it so the server treats them as capable clients. The guard tests that already pass before the fix (caps already present, no df_meta.stats, ignored aborts, unknown types, infinite_resp pairing) are added here, with a makeModel(null) helper so a model without stats can be built. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ows-first c4) Jest tests for the scheduler (StateOrchestrator rewritten around an IModel: a stats_request after the first infinite_resp, one request per reply until final, nothing after a state change, nothing for a search_string-only change), for the status bar's stats column and Compute summary stats control, for the widget and BuckarooView wiring of that control, and for pinned rows in the error state. Playwright tests on Storybook cover the four states without layout shift and the control's request, and the server spec runs the real standalone page against a session whose first frame says the stats are pending. StateOrchestrator.ts, StatusBar.tsx and BuckarooWidgetInfinite.tsx carry only the signatures the tests use, so the tests fail on assertions. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tus layout and the control wiring (rows-first c4) Cases found untested or mis-specified after the first push, written against the same stubs: a model that starts complete waits out the delay for its first state change, as does a change made after the earlier stats completed; a request's time is measured once; the Compute summary stats button calls its handler with no arguments; the standalone page offers that button for a session whose stats are not computed and asks for nothing until it is clicked. The layout spec now says what holds across statuses (nothing above or beside the grid moves, and the grid's height follows the pinned area), and the story loads the widget's stylesheet as the real page does. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…g, not computed and error states (rows-first c4)
StateOrchestrator becomes the client scheduler for the stats wire. It takes an
IModel, so it sends stats_request {stats_gen, scope: "raw"} through model.send
and watches change:df_meta, change:df_data_dict, change:buckaroo_state and
msg:custom. It asks once the first infinite_resp has arrived (or after 1.5 s
without one), asks again for each reply that leaves df_meta.stats pending, and
stops when the status changes. A change to post_processing, cleaning_method or
quick_command_args waits out a delay of twice the last request's time (200 to
3000 ms) before asking for the next gen; a search_string-only change, or any
other field, leaves the schedule alone. Nothing is requested unless
df_meta.stats says pending.
WebSocketModel starts the scheduler, so BuckarooServerView and the standalone
page get it with no wiring of their own. The status bar gets a fixed-width stats
column, for sessions that report df_meta.stats, showing loading, a Compute
summary stats button that sends stats_request {force: true}, the error reason, or
ready. Valueless pinned rows are omitted in the error state, as in not_computed.
requestStats and StateOrchestrator are exported for hosts with their own IModel.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…umns hint (rows-first c4b) The scheduler and the Compute summary stats control should send stats_request with incremental: true and, when the grid has reported them, the columns it shows as the columns hint. A run is one request per partial reply, with the stats pending until the final reply, and a gen change mid-run stops it and starts one for the new gen. Jest: the scheduler's request shape, hint and chain against a fake model and against a real WebSocketModel (partial replies, a final that carries the complete all_stats, a whole-run final from a server that ignores the field, a gen change mid-run), requestStats, BuckarooView's callbacks, and the grid reporting its viewport columns. Playwright: the server spec (a harness answers stats_request over a real session, including a wide table whose hint follows a horizontal scroll) and the Storybook control. The existing request assertions gain incremental: true. The two unused on_visible_columns props are stubs so the tests compile. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… test releases a reply (rows-first c4b) The grid reports the columns it shows a frame after it renders them, and the wide-table test released the reply as soon as the header cells had changed, so on a loaded machine the next request could go out with the earlier hint. Wait two animation frames first. No assertion changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ith a columns hint (rows-first c4b) requestStats, which the scheduler and the Compute summary stats control both use, now sends stats_request with incremental: true and, when the grid has reported them, the columns it shows as the columns hint, read from the model's visible_columns key when the request goes out. A server on the unit path answers with a partial stats_update per request, the scheduler asks again for each, StatsChannel merges them without leaving "pending", and the final reply with the complete all_stats ends the run. A server that ignores the fields answers with a whole-run final, which ends it the same way. DFViewerInfinite takes on_visible_columns and reports the data columns that have a header cell in the grid's viewport on grid ready and on onVirtualColumnsChanged. BuckarooInfiniteWidget forwards it, and BuckarooView and the standalone page store the list on the model with setVisibleColumns. The columns are read from the rendered header cells because the grid's column API is not registered, and registering it would switch on the column-state calls that are inert today. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rol and demand units (rows-first c5)
Tests for plan 3 phase 5, with the stubs they compile against (empty
StatsEmptyState, forceStats and the df_meta.stats helpers return nothing):
- a not_computed session renders typed columns with no blank pinned rows, the
summary view shows an empty state with the compute control, and for reason
"ceiling" a message and no control
- auto_request false leaves the scheduler idle except for demand_columns, which
it asks for as one scoped request
- the control sends stats_request {force, tier} with the tier taken from
requestable (scalar before full), and a per-column form names the columns;
the scheduler continues a forced run
- a final stats_update with status not_computed and reason ceiling ends in the
ceiling message
- the client advertises ?caps=stats_update,stats_ondemand from both wiring
copies
Existing assertions of the capability URL and of the control's request change
because the request shape changes by design.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ing and finished demand runs (rows-first c5) Found while running the first test commit's behaviour end to end against the p33 server: - the control marks the stats pending on the client, since the server sends no frame to a capable client, so the loading text shows at once and a second click cannot start a second chain of requests; a refusal or a run for some columns returns the session to not_computed, and a full frame for the same gen ends the run - a session the server sized at the ceiling itself (reason "size", requestable empty) reads as over the limit: the message and no control - a demand run that has ended is not asked for again when a later frame arrives, and a full frame for the same state does not end one in flight Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…or the stats policy (rows-first c5)
The client side of plan 3 phase 5, against the df_meta.stats fields of the
policy wire (p33): tier_target, reason, auto_request, requestable, estimate,
demand_columns, omitted_keys and approx_keys.
- ?caps= carries stats_update and stats_ondemand, from both wiring copies, so a
server applies its policy to this client.
- A final stats_update may carry status and reason. {final: true, status:
"not_computed", reason: "ceiling"} with no payload ends in the ceiling
message; a reply with a payload and a status merges it and leaves that status.
- The control is forceStats: stats_request {force, tier}, the tier being the
smallest requestable above the one reached (scalar before full), with a
per-column form that names columns. It marks the stats pending, and the
scheduler continues the run, one request per reply that is not final.
- With auto_request false the scheduler asks only for demand_columns, as one
scoped request per gen and list of columns.
- The status bar says why the stats are missing and offers the control unless
the ceiling refused them. The summary view shows an empty state with the
reason, the size and the control, and a column picker, in place of its grid.
Nothing changes for a session whose df_meta.stats is absent.
Needs a buckaroo-js-core release and a tallyman bump.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…control's clicks (rows-first c5) The Storybook Playwright job failed on the c5 fix commit: clicking "Compute summary stats" in the status bar timed out with ".ag-body-horizontal-scroll-container from .ag-body-horizontal-scroll.ag-scrollbar-scrolling.ag-scrollbar-invisible subtree intercepts pointer events". The control's button was 20px high in a 20px row, so the browser scrolled the grid to reveal it, AG Grid flagged the horizontal scrollbar as scrolling, and its overlay (positioned over the last 16px of the one-row grid, not drawn) sat above the button. - The button is 16px high and its container fills the row, so it fits inside it. - The status bar's invisible scrollbar overlay takes no pointer events. A scrollbar the browser draws in its own space (no ag-scrollbar-invisible) is left alone, and the strip still scrolls with the wheel. Checked with the overlay held in its scrolling state: a click on the control fails with the CI message on the previous CSS and lands with this one. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…mns computed (rows-first c5) A scoped run (the per-column form, or the demand columns) ends with status not_computed and a payload that StatsChannel merges into all_stats. The summary view shows the empty state for as long as the status is not_computed, so those stats are not shown anywhere. The tests pin a df_meta.stats.computed_columns list that StatsChannel sets when a run ends not computed, and the summary view showing its grid once the list is not empty. DFMetaStats carries the field as a type only, so the failures are assertions. Two assertions of this branch's own tests that read the end state of a scoped run now expect the list. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…mmary view (rows-first c5) A run for some columns ends with status not_computed and a payload, and StatsChannel merges the payload into all_stats. The summary view showed the empty state for as long as the status was not_computed, so the stats of the column the user picked, or of the demand columns, were not shown. StatsChannel now keeps the columns the replies of a run filled (a column counts where some stat row has a value for it) and a final reply or a not_requestable refusal that leaves the session not computed puts them in df_meta.stats.computed_columns. A new gen, or a frame that replaced the stats, starts the list over. The summary view shows its grid, not the empty state, when the list is not empty. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… and for the tier a session has reached (rows-first c5b) A session the server sized to scalar is never requested (desiredRun returns nothing for not_computed while auto_request is true), and the compute control asks for scalar on every click because df_meta.stats.tier stays schema after a scalar run. The tests cover the scheduler (the target tier is requested once, continued to its final reply, not requested for auto_request false, ceiling, cost or a reached target), the channel (a final reply to a whole-table request records the tier reached, reset by a new gen), the control (scalar, then full, then nothing), the status bar label and the Storybook flow. tierReached and autoRequestTier are stubs here. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Contributor
📦 TestPyPI package publishedpip install --index-strategy unsafe-best-match --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ buckaroo==0.15.9.dev37355456647or with uv: uv pip install --index-strategy unsafe-best-match --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ buckaroo==0.15.9.dev37355456647MCP server for Claude Codeclaude mcp add buckaroo-table -- uvx --from "buckaroo[mcp]==0.15.9.dev37355456647" --index-strategy unsafe-best-match --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ buckaroo-table📖 Docs preview🎨 Storybook preview |
…session has reached (rows-first c5b) The scheduler gains a target run: a not_computed session whose auto_request is not false and whose tier_target is above the tier reached (autoRequestTier) gets one incremental stats_request for that tier, not forced and with no columns, after the first rows. The run marks the stats pending as forceStats does, is continued for each reply that is not final, ends on a final reply, a refusal or a new gen, and is not started again for the state it ended in. A reason of ceiling or cost, auto_request false and a schema target request nothing. requestStats records the request it sends under stats_requested, and StatsChannel reads it to tell a final reply to a run for the whole table (a tier and no columns, with a payload, leaving the session not computed) from a refusal or a run for some columns. Only the first records df_meta.stats.reached_tier, the higher of what was there and the reply's tier. A frame from the server replaces df_meta, so a new gen resets it. tierReached reads the server's tier and reached_tier, nextRequestTier and demandTier read tierReached, so the control asks for scalar, then full, then nothing. A request that names a tier no longer carries the grid's columns hint. The status bar names the tier on screen and replaces the control with a computed label when no tier is left. One of the phase's own tests had a timer step that never reached its request (it advanced the first-paint timeout in one step, which leaves the zero-delay request timer queued); it now steps as the existing first-paint test does. The guard tests that pass on the code before the fix are added here. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
paddymul
changed the base branch from
main
to
adr-003-stats-tiers-and-size-policy
October 6, 2026 14:40
Collaborator
Author
|
Closing. Under the revised D2 of ADR-002 (#1043, 7943412) the server pushes the policy's target tier itself, and it should report the tier reached in The status-bar tier labels still apply and are the part to carry into a client UI PR on top of #1025. The branch is kept for them. |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An end-to-end browser run on the merged stack (the server phases and the client of c5, PR #1032) found two defects in the client.
A scalar target is never requested. With
BUCKAROO_STATS_FULL_AUTO_ROWS=5000000andstats_tierautoon a 10.8M-row parquet, and with a host-namedstats_tierscalar, the first frame is{status: "not_computed", reason: "size"}("host"for the host's choice) withtier_target: "scalar"and noauto_request(absent means true). The client sent 0stats_requestmessages.desiredRuninStateOrchestrator.tsreturns nothing fornot_computedwheneverauto_requestis true, because onlypendingis run automatically and demand runs needauto_requestfalse. The server contract (the module docs ofstats_policy.pyandstats_wire.py) saysauto_requestmeans the client should request up totier_targeton its own.The compute control is stuck at scalar. With
stats_tierschema(first frameauto_request: false,requestable: ["scalar", "full"]), three consecutive clicks each sent{tier: "scalar", force: true, incremental: true}. Each reply was a finalstats_update {tier: "scalar", status: "not_computed"}, anddf_meta.stats.tierstayed"schema", so the next click chose scalar again and the status bar kept the same button. The client had no record of the tier a run reached.Those are the numbers from the integration run. I reproduced both against a real server before and after the change, at a smaller scale (see Tests, "End to end").
Phase and plan references
Client phase c5b, a follow-up to c5 (
plans/03-no-summary-stats-for-large-files.md, section 6 "Phase 5", with sections 3.2 and 3.4). Also plan 1 sections 3 and 4.0 and plan 2 sections 3 and 4.1. The server contract is in the interface notes of p33 (#1029:auto_request,tier_target,requestable) and p34 (#1031: a scalar run changes nothing on the session, and its replies carrytier: "scalar").Approach
Scalar target.
autoRequestTier(stats)(WidgetTypes.tsx) is the tier the client should request on its own:tier_targetwhen the status isnot_computed,auto_requestis not false, the reason is notceilingorcost, the target is a tier above schema, and the tier reached is below it. It ignoresrequestable, which lists the tiers above the target. The scheduler gains a run kind, the target run. After the first rows (or the first-paint timeout) it sends onestats_request {stats_gen, scope: "raw", incremental: true, tier}, not forced, and marks the stats pending in a newdf_metaasforceStatsdoes, since the server sends no frame to a capable client while it runs. It then answers each reply that is not final (a newdf_data_dictunder the samedf_meta) with the same request. A final reply, a refusal or a new gen ends it, and it is not started again for the state it ended in. Withauto_requestfalse the scheduler still idles except fordemand_columnsruns, and reasonceilingrequests nothing.Tier reached. The server leaves
df_meta.stats.tieratschemaafter a scalar run, and a final reply names the tier that was asked for, not whether it ran: a refusal (ceiling,cost) carries the asked tier too, and nothing in the reply says whether the run covered the whole table or named columns. So the client keeps its own record.requestStatsrecords the request it sends on the model understats_requested({gen, tier?, columns?}), before sending it.StatsChanneltakes a finalstats_updateas the run that reached its tier for the whole table when it left the sessionnot_computed, carries a payload (a refusal has none), and its tier and gen are those of the last request, which named a tier and no columns. It then setsdf_meta.stats.reached_tierto the higher of that tier and the one already there. A run for some columns, a refusal,stats_aborted, and a reply to no request record nothing.reached_tierlives indf_meta.stats, which every frame from the server replaces together withall_stats. A new gen therefore resets it, and the tier never outlives the stats it describes.StatsChannelis the only writer.tierReached(stats)is the higher of the server'stierandreached_tier.nextRequestTier, and socanRequestStatsandforceStats, read it: the first click asks for scalar, the second for full, and then nothing is left. A demand run is skipped once the tier reached covers the tier it would ask for.Request columns. A request that names a tier no longer carries the grid's visible-columns hint, since the server reads
columnswith atieras the scope of the run. The control and demand requests already carried none or their own columns, so only the new target request is affected.Status bar. For a
not_computedsession that has reached a tier above schema, the cell names the tier on screen ("Basic stats" after scalar) beside the control for the next tier ("Compute full stats"). When nothing is left to request, or there is no handler, the control is replaced by a label: "Basic stats computed" (the tooltip says what scalar covers), or "Summary stats computed" for full. This holds under the ceiling too, since the stats on screen are not unavailable. A session that has reached nothing reads as before ("Compute summary stats", "Continue computing stats", "Summary stats unavailable: size limit").What changes
src/components/WidgetTypes.tsx:reached_tier,higherTier,tierReached,autoRequestTier;nextRequestTieranddemandTierread the tier reached.src/server/StatsChannel.ts:STATS_REQUESTED_KEYandStatsRequested;reached_tieron a final reply.src/server/StateOrchestrator.ts:requestStatsrecords the request and drops the hint when a tier is named; the target run in the scheduler;StatsModelnow includesset.src/components/StatusBar.tsx,src/components/StatsEmptyState.tsx(exportsTIER_DETAILS): the tier label.Tests
Tests-only commit
fea8ba9a, pushed before the fix. Locally, 37 jest tests and the 4 new Storybook Playwright tests failed on it, all on assertions. The Playwright tests run a newTierRunsstory: the real model, channel, scheduler and status bar against a scripted server. On CI exactly JS / Build + Test and Storybook Playwright Tests failed; the other 26 entries completed without failure (25 SUCCESS, including the Read the Docs status, and deploy SKIPPED). The tests that already pass on the code before the fix are not in that commit: 29 test cases, namely the negative cases ofautoRequestTier, the channel's "records nothing" cases, the scheduler's "sends nothing" cases and the status bar's unchanged labels. They are added with the fix, as in the earlier phases.Fix commit
488a6a5a. On CI all 28 entries completed, 27 SUCCESS (26 check runs and the Read the Docs status) and deploy SKIPPED, including JS / Build + Test, Storybook Playwright Tests, Server Playwright Tests and all nine Python / Test jobs.WidgetTypes.test.ts(tierReached,nextRequestTier,autoRequestTier),StatsChannel.test.ts(the tier a final reply reached, through a realWebSocketModel),StateOrchestrator.test.ts(the target run, the control's tier, and a "tier bookkeeping" block end to end throughWebSocketModelwith a fake socket: target to final reply, gen change, refusal, ceiling reply, scalar then full, a server that allows scalar only, a run for one column),StatusBar.stats.test.tsx(the labels).stats-scheduler-states.spec.ts: a scalar target is requested without a click and the status bar names the tier; the control asks for scalar, then full, and is gone after the full run; a server that allows scalar only leaves a "Basic stats computed" label; a new gen starts over.Local runs before the fix push: jest 636 passed (35 suites),
tsc -bclean,pnpm run build. Storybook Playwright with one worker and no retries:stats-scheduler-states13 of 13, and the other four files CI runs 12 of 12. The Python unit suite (no Python file changed) gave 1182 passed and 5 skipped, with 3 lazy-widget tests failing in the full run (test_stats_clear_status_on_success,test_execution_update_messages,test_lazy_widget_init_should_not_block_but_does_with_mp_and_slow_exec) that pass alone (14 passed), the shared~/.buckaroosqlite case.Server Playwright, locally: 53 of 54 on the first full run and 54 of 54 on the second. The failing test is
server-buckaroo-search.spec.ts"searching filters the table data, not just the status bar count", which the c5 PR also recorded. It is not caused by this PR. On the base bundle (this PR's changes stashed, bundles rebuilt) four runs of that spec against a freshly started server gave a failure on the first run (0.9 s) and passes on the next three (3.7 s each). That session has no stats request. The test reads the text of.df-vieweras soon as the first.ag-cellis visible, and the first window of rows can come back after the pinned-row cells render. I did not pin why it is only the first run on a fresh server process. I suspect the first buckaroo-mode load in a process is slower (imports); warming the process with a throwaway session, or waiting for a row cell in the test, would decide it. The test is not changed here.End to end. A scratch checkout of the integration branch (server phases p33, p34, p36a, p37 and the c5 client) with this PR's two commits cherry-picked, a real
python -m buckaroo.serveron a free port, a 50,000-row by 5-column xorq memtable build, and the standalone page driven by Playwright Chromium with the WebSocket frames read. The scale is 50,000 rows withBUCKAROO_STATS_FULL_AUTO_ROWS=10000, in place of 10.8M rows with 5,000,000, so the policy path is the same but the timings are not those of the large file. Nothing from that checkout is committed.auto, size:not_computed, reasonsize,tier_targetscalar{tier: "scalar", incremental: true}with no click; reply final,statusnot_computed, reasonsize, payload; the cell reads "Basic stats" with "Compute full stats"; one click runs full (2 requests), then "Summary stats ready" and no controlscalar, host: reasonhost,tier_targetscalarhost(1 request, then 1 forced full request after the click)schema:auto_requestfalse,requestablescalar and full{tier: "scalar", force: true, incremental: true}; the button never changedBUCKAROO_STATS_CEILING_SCALAR_CELLS=1000),autoandscalar: reasonceiling,auto_requestfalse,requestableemptyNo test line of an earlier phase was edited (the test diffs add lines only). One test of this phase's own tests commit was corrected in the fix commit: it advanced the first-paint timeout in a single step, which leaves the zero-delay request timer queued, so it now steps as the existing first-paint test does.
Why default behaviour is unchanged
A session with no
df_meta.stats, apendingsession, and anot_computedsession whoseauto_requestis false or whose reason isceilingorcostsend what they sent before. The target run starts only for a session that a server with the policy reports asnot_computedwith a target above schema andauto_requestnot false, and only to a client that advertisesstats_ondemand. That needs the host to opt in withstats_tieror a server that resolvesauto; the server default is unchanged.requestStatsnow also writes one model key on every request, which onlyStatsChannelreads. The hint is dropped only from requests that name a tier, and those were the control's (no hint already), the demand run's (its own columns) and the new target request.Deviations from the plan
df_meta.stats(reached_tier), derived from the client's own last request and the tier of the final reply. The task text asks for it to be tracked from the tier of final replies. The reply alone cannot say whether it covered the whole table, so the request is part of the rule.columns, so a scalar target covers the whole table. The visible-columns ordering hint of c4b is not sent with a tier.StatsModel(the scheduler's view of a model) andrequestStatsnow needset, to record the request. EveryIModelhas it.tier_targetfullwithnot_computedandauto_requesttrue is requested the same way (the tier is the target); no server sends that today.Not in this PR
columnsof its run (or a scope), or adf_meta.stats.tierthat moved to the highest tier a run reached, would replace the request record. The client is correct without either.all_statsandreached_tiertogether, and the scalar target is not requested again for the same gen after its run ended.buckaroo-js-corerelease.Stack
Built on
feat/rowsfirst-c5-client-not-computed-control(#1032), which contains c4b (#1030), c4 (#1027), c2 (#1025) and c0a (#1020). The PR targetsmain, so the diff includes their commits until they merge: c0a29e1e42cand405f4ded; c2fe046acc,3118435a,1fd9cff8and5935432d; c4dd6ebe6a,d6afbf98and95dcceec; c4bd7eda002,5d1e0467ande5649605; c501609fa3,f2c4c0a4,17376859,7b30aad1,30bf7c02and9992b2e2. This phase's commits arefea8ba9a(tests) and488a6a5a(fix). Read only those.🤖 Generated with Claude Code