Repository navigation
feat(client): not_computed states, compute control and demand units for the stats policy (rows-first c5) - #1032
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>
📦 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.dev37206648816or 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.dev37206648816MCP server for Claude Codeclaude mcp add buckaroo-table -- uvx --from "buckaroo[mcp]==0.15.9.dev37206648816" --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 |
…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>
Review of the not_computed control: one finding, fixedFinding (medium): the per-column compute form produced stats that no view showed. A scoped run (the per-column picker, or the demand columns) ends with Addressed in
Verification. Existing tests changed. Two assertions in this branch's own c5 tests read the end state of a scoped run with Limits, not changed here.
Unrelated failure seen once. In the first full Python run after the test commit, 🤖 Generated with Claude Code |
|
Closing. The scheduler half (demand-column runs, continuing a forced run) is client pull, which the revised D2 of ADR-002 (#1043, 7943412) drops. The UI half still applies: |
Problem
Plan 3 (
plans/03-no-summary-stats-for-large-files.md) lets the server leave a large entry without summary stats. The wire for that is in the server branch #1029 (p33):df_meta.statsgainstier_target,reason(size | host | cost | ceiling),auto_request,requestable,estimate,demand_columns,omitted_keysandapprox_keys, and a client that sends?caps=stats_update,stats_ondemandis toldnot_computedwith those fields instead of being sent complete stats at connect. The client of c0a to c4b knowsnot_computedonly as a status. It has no empty state for the summary view, no message for a refusal, no tier on the control's request, never asks fordemand_columns, does not continue a forced run, and advertises onlystats_update, so a p33 server would never apply the policy to it.Phase and plan references
Client phase c5: plan 3 section 6 "Phase 5", with sections 3.2 (capabilities, cost guard), 3.3 (what the grid shows, demand-driven minimum) and 3.4 (the on-demand control). Also plan 1 sections 3 and 4.0 and plan 2 sections 3 and 4.1. The field names and defaults follow the interface notes of p33 (#1029):
auto_requestabsent means true,requestableabsent means["full"].Approach
Capabilities.
withStatsCapabilityaddsstats_updateandstats_ondemandto?caps=. Both wiring copies call it (BuckarooServerView.tsxandpackages/js/standalone.tsx), so both advertise the second bit. This client can rendernot_computedwith its reason and send tiered requests, which is what the bit promises.Types and helpers (
WidgetTypes.tsx).DFMetaStatscarries the policy fields.statsRequestableandstatsAutoRequestread the documented defaults.nextRequestTierreturns the smallest tier inrequestableabove the tier reached, so scalar goes before full, and scalar having been reached the next one is full.canRequestStatsis true for anot_computedsession with such a tier and a reason other thanceiling.demandTieris the smallest tier from scalar up that the policy allows, which is the one that carries min and max.Reply handling (
StatsChannel.ts). A finalstats_updatemay carrystatusandreason.{final: true, status: "not_computed", reason: "ceiling"}with no payload setsdf_meta.statsto the same stats with the new reason and leavesall_statsalone, so the session ends in the ceiling message. A reply that carries a payload and a status merges the payload and takes the status, which is the shape a scoped run for some columns needs. The columns the run's replies filled go indf_meta.stats.computed_columns, a field the client sets and the server never sends: a column counts where some stat row holds a value for it, a final reply or anot_requestablerefusal that leaves the session not computed adds them, and a new gen, or a frame that replaced the stats, starts the list over. A final reply with no status still completes the session and drops the policy fields, as before. The reply'stiercounts only when the session completes.The control (
StateOrchestrator.ts).forceStats(model, opts?)sendsstats_request {stats_gen, scope: "raw", incremental: true, force: true, tier}with the tier fromnextRequestTier, and sends nothing when no tier is left.opts.columnsis the per-column form of plan 3 section 3.4 and addscolumns. The whole-table request carries nocolumns: on a forced requestcolumnsnames what the request is for, so the grid's visible columns (an ordering hint on the scheduler's requests) are not added to it.requestStatstakestierandcolumnsoptions.forceStatsrecords the run on the model understats_forced.Scheduler. It acts on two kinds of work besides the pending run of c4:
auto_requestfalse it is idle, except that it asks fordemand_columns: one scoped request{incremental: true, columns: demand_columns, tier}per gen and list of columns, after the first rows, at the tierdemandTiergives, with noforce. A reply that is not final (a newdf_data_dictunder the samedf_meta) is answered with the next request, a final reply or a refusal (a newdf_meta) ends it, and a changed list or a new gen starts it again. A pending session withauto_requestfalse is not run whole.forceStatsis continued: each reply that is not final is answered with the sameforce,tierandcolumns, and a final reply, a refusal or a new gen ends it. c4b left this open because a forced request had nothing to continue it.Sessions that auto-request keep c4's behaviour. A
not_computedsession withauto_requesttrue (a scalar target before the server has scalar units) is left alone.What the user sees. In the status bar the stats cell for
not_computedshows the "Compute summary stats" button whencanRequestStats, "Continue computing stats" when the reason iscost, the message "Summary stats unavailable: size limit" and no button forceiling, and the old label when nothing is requestable. The button calls the host's callback with no arguments. In the summary view,StatsEmptyStatereplaces the grid while the status isnot_computedand no run has computed a column (computed_columnsis empty): it says why (size, host, cost, ceiling), with the size fromestimate("12.4M rows x 44 columns"), and offers one button for the next tier ("Compute basic stats" for scalar, "Compute full stats", "Continue computing stats" after a cost pause) with a per-column picker. Forceiling, or when nothing is requestable, it is the message alone. Once a run for some columns (the per-column form, or the demand columns) has filled some, the summary view shows its grid with those stats in place of the empty state. The per-column picker is then off screen, and the status bar's control still asks for the whole table. The main view keeps its grid, with the typed columns and, from c0a, no blank pinned rows. A session whosedf_meta.statsis absent behaves as before.on_compute_statsnow takes optional{columns}.BuckarooViewand the standaloneBuckarooApppass(opts) => forceStats(model, opts);forceStatsis exported next torequestStats.Status bar geometry (
dcf-npm.css). The control's button is 16px high inside a container that fills the status bar's one row, and the status bar's invisible scrollbar overlay takes no pointer events. See Tests for the CI failure that led to this.What changes
src/components/WidgetTypes.tsx: the policy fields and the helpers.src/server/StatsChannel.ts: the capability pair; the status and reason of a final reply;computed_columns.src/server/StateOrchestrator.ts:requestStatsoptions,forceStats,FORCED_RUN_KEY, demand and forced runs in the scheduler.src/components/StatusBar.tsx,src/components/StatsEmptyState.tsx(new),src/components/BuckarooWidgetInfinite.tsx,src/style/dcf-npm.css: the states and the control's geometry.src/server/BuckarooView.tsx,packages/js/standalone.tsx,src/index.ts: the wiring and the export.Tests
Two tests-only commits, pushed before any fix:
01609fa3andf2c4c0a4. On each, exactly three checks failed (JS / Build + Test, Storybook Playwright Tests, Server Playwright Tests) and every Python check and the other jobs passed.f2c4c0a4pins cases the first commit left untested: the control's pending state, a size the server chose with nothing above it reading as the ceiling, and demand runs that have finished. Where a test needs a symbol that does not exist yet, the commit carries a stub that keeps the old behaviour, so the failures are assertions.Review follow-up, one more pair:
30bf7c02(tests) and9992b2e2(fix). A run for some columns ends with its stats merged intoall_statsand the status stillnot_computed, and the summary view showed the empty state for that status, so no view showed them. On30bf7c02, JS / Build + Test failed. On9992b2e2all 28 entries completed, 27 SUCCESS and deploy SKIPPED.WidgetTypes.test.ts(defaults,nextRequestTier,canRequestStats,demandTier),StatsChannel.test.ts(the capability pair, a final update with a status),StateOrchestrator.test.ts(demand runs,forceStats, continued forced runs, a not_computed session end to end),StatsEmptyState.test.tsx(new),StatusBar.stats.test.tsx,BuckarooInfiniteWidget.flash.test.tsx(the summary view's empty state, and its grid after a run for some columns),BuckarooView.stats.test.tsx,BuckarooServerView.caps.test.tsx.stats-scheduler-states.spec.ts: five new tests (the control's message, typed columns with no blank pinned rows, the empty state, the ceiling, a paused run) plus the status bar's control, on theStatsSchedulerStatesstory.server.spec.ts: four new tests against the standalone page (the URL carries both capabilities; a policy session asks for nothing and the control asks for the basic tier; a request over the ceiling ends in the ceiling message; a forced run is asked for again after each partial reply and ends with the final one).Fix commits:
17376859, the implementation. On CI, JS / Build + Test and Server Playwright Tests passed and Storybook Playwright Tests failed: the click on "Compute summary stats" timed out in headless Linux Chromium with "ag-body-horizontal-scroll-containerfromag-body-horizontal-scroll ag-scrollbar-scrolling ag-scrollbar-invisiblesubtree intercepts pointer events". It passed on macOS. The condition is deterministic: the button was 20px high in a 20px row, so the browser scrolled the grid to reveal it, AG Grid putag-scrollbar-scrollingon the status bar's horizontal scrollbar overlay, and the overlay (positioned over the last 16px of a one-row grid, not drawn) sat above the button.7b30aad1, CSS only. Reproduced locally by holding the overlay in its scrolling state and clicking the button: it fails with the CI message on the CSS of17376859and lands on this one.CI on
7b30aad1: all 28 entries completed, 27 SUCCESS (26 check runs and the Read the Docs status) and deploy SKIPPED.Local, before the push of
7b30aad1: jest 557 passed,tsc -bclean, the five Storybook Playwright files CI runs pass with one worker and no retries (stats-scheduler-states 9 of 9), server Playwright 54 of 54 on the second full run. The first full run after rebuilding failed its first test,server-buckaroo-search.spec.ts"searching filters the table data": it reads the text of.df-vieweronce the first.ag-cellis visible and found no row text. The same test failed in two earlier full runs on this branch, before the CSS change, passes alone three times in a row, and passed in the next full run and on CI for both fix commits (CI retries twice). I did not pin its governing variable. I suspect the firstinfinite_resparriving after the first pinned-row cell on a freshly started server; delaying that frame would decide it. The test is not changed here.Why default behaviour is unchanged
Nothing new is requested unless
df_meta.statscarries the new fields or a status other than pending, which only a session on a server with the policy reports, and only to a client that advertisesstats_ondemand. A session with nodf_meta.statssends nostats_requestand renders as before. A session atpendingwith noauto_requestfield runs whole, as in c4. The summary view keeps its grid for every status butnot_computed. The grid registers no new AG Grid module.The one change a default session can see is the URL: the client now asks for
stats_update,stats_ondemand. A server that predates the policy ignores the second bit. A server with the p33 policy (#1029) applies it to this client, and a session it resolves belowfullis shown asnot_computed. That needs the host to opt in withstats_tieror a server that resolvesauto; the server default is unchanged. The status bar CSS applies to the stats control only and to the status bar's invisible scrollbar overlay.Existing tests changed, because the behaviour they pin changes by design: the
?caps=assertions (StatsChannel.test.ts,BuckarooServerView.caps.test.tsx, and the standalone URL test inserver.spec.ts) expectstats_update,stats_ondemandinstead ofstats_update, and the assertions of the control'sstats_request(BuckarooView.stats.test.tsx,StateOrchestrator.test.ts,server.spec.ts,stats-scheduler-states.spec.ts) no longer expect the c4bcolumnshint on that request (see the first deviation). TheStatusBar.stats.test.tsxhelpers take the callback's new optional argument,routeStatsRequestsinserver.spec.tstakes an optional first-frame stats argument with the old value as its default, and the story's control callsforceStats. Two assertions of this branch's own tests that read the end state of a scoped run now expectcomputed_columns(StatsChannel.test.ts, "merges a payload it carries...", andStateOrchestrator.test.ts, "a run for some columns ends not computed..."), because that end state changed by design. No assertion about other behaviour was loosened.Deviations from the plan
columns. Plan 3 section 3.4 gives the control's message as{stats_gen, scope, tier, force}with acolumnsform per column. c4b added the visible-columns hint to every request, the control's included. On a forced requestcolumnsis a scope, so the hint is dropped from it.requestableand the tier reached (smallest first). Plan 3 does not say how.stats_forced) thatforceStatssets. c4b's interface notes list this as an open gap. How the server answers a forced request (a partial reply, then a final one) is the p33 and phase 6a contract; against a server that refuses it withnot_requestablethe run ends at once.{incremental, columns, tier}with noforce. Plan 3 section 3.3 says "an automatic scopedstats_requestwith acolumnshint, under the same policy". The wire shape of the reply to it (a final reply withstatus: "not_computed"and a payload) is my reading, for phase 6a to confirm.statusandreasonon a finalstats_updateis a new part of the wire. Plan 3 section 3.4 gives the ceiling reply asstats_update {final: true, status: "not_computed", reason: "ceiling"}; takingstatuson any final reply is the general form.not_computed. The empty state replaces the grid only while no run has computed a column. After a run for some columns the grid is shown, and the per-column picker, which lives in the empty state, is no longer on screen; asking for more columns goes through the status bar's whole-table control.costis handled as a reason ondf_meta.stats(the button reads Continue). The server half,cost_pausedandelapsed_ms, is phase 6a.omitted_keysandapprox_keysare typed but not shown anywhere.Not in this PR
not_computedsession is answeredstats_aborted not_requestable, so the control does nothing visible until 6a.df_display_argsis still not applied.buckaroo-js-corerelease and a tallyman bump, which are not part of this PR.Stack
Built on
feat/rowsfirst-c4b-client-incremental-requests(#1030), which contains 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. This phase's commits are01609fa3andf2c4c0a4(tests),17376859(implementation),7b30aad1(status bar CSS), and30bf7c02(tests) with9992b2e2(fix) for the review finding about the per-column form. Read only those. The server branches (#1024, #1028, #1029) are not part of this PR.🤖 Generated with Claude Code