[draft] bt-ui: single page UI - #7127
Draft
gignat-dev wants to merge 45 commits into
Draft
Conversation
gignat-dev
force-pushed
the
dev/gignat/bigtrace_spa
branch
7 times, most recently
from
August 25, 2026 09:00
dce07e7 to
b2a7ee0
Compare
The History sidebar split runs across two sub-tabs (Ephemeral / Persistent), so you could never see both at once and every Run had to point the sidebar at the matching tab. Replace the tabs with a single list plus an All / Ephemeral / Persistent segmented filter, defaulting to All. - history_store: `filter` (all/ephemeral/persistent) replaces `activeTabKey`; a new pure `filterHistory()` does the narrowing. `setHistoryActiveTab` and its three call sites in the editor toolbar / query page are gone. - query_history: fixed toolbar (filter + refresh) above a scrolling list. - query_history_item: the kind now comes from the entry itself, and each row carries an Ephemeral / Persistent marker since the list mixes both. - The flat segmented control the preset picker used moves to widgets/segmented.ts so both surfaces share it.
The right sidebar carried a second tab browsing the PerfettoSQL stdlib (fetched from stdlib_docs.json). It is not part of querying at scale, and it cost the History panel a tab strip. Delete it: History is now the whole sidebar, rendered directly with no Tabs wrapper. Removes sql_tables.ts, table_list.ts and sql_modules.ts (nothing else referenced them, and the stdlib_docs.json fetch goes with them), and renames the sidebar command to bigtrace.ToggleHistorySidebar now that there is only one thing in there.
Every query started at 100 rows regardless of how it ran, and the trace fan-out cap was whatever the backend's global default happened to be. Tie both to the execution mode instead: ephemeral runs 1k rows over 10k traces (a quick look), persistent runs 10k rows over 100k traces (a full sweep). - query_tabs_state: MODE_DEFAULTS + helpers over the trace cap (explicit/effective/set/clamp). New tabs seed both caps from their mode; applyModeDefaults() moves them when the mode flips, but only where the value still holds the old mode's default — anything typed by hand, or set by a preset, survives. A run with no explicit cap ships the mode default rather than the backend's global one. - editor_tab_view: the toolbar now shows Rows and Traces side by side, so what a run costs is visible without opening settings. The Traces control hides itself when the backend doesn't declare a trace cap. - The backend's declared min/max for a numeric setting now survives onto the descriptor (it was folded into the zod schema and lost), so a default we seed ourselves can be clamped to what the deployment accepts. The trace cap is a backend setting, so the UI has to name it: that coupling is confined to TRACE_LIMIT_SETTING_ID and degrades to "no cap control" when the backend doesn't advertise it.
Groundwork for the new-query launcher: presets become the unit of configuration, so users need to save their own alongside the backend catalog. A LocalPreset is just a TracePreset with an isLocal marker, so both kinds render and apply through one code path; only the launcher's rename / delete affordances branch on it. - localPresetStore: one localStorage key, CRUD (save / overwrite by id / rename / remove / findByName for the overwrite prompt). Entries missing an id, name or SQL are dropped on read — the key is user-editable — and writes swallow quota errors rather than failing a save. - presetFromTab() captures the EFFECTIVE settings, so a saved preset reproduces its run without depending on what else is registered. - lastPresetIdState remembers what the last new query started from (the launcher preselects it); lastSetupState remembers the trace selection a query last ran with, to seed the launcher's custom form. Store only — nothing renders it yet.
A new query used to open on an empty editor inheriting whatever the global settings page happened to hold. Presets are becoming the unit of configuration, so a tab now starts on a launcher: pick a preset (backend catalog or one you saved), or configure the traces and options yourself. - Tabs gain `configured`; while false the tab renders QueryLauncher instead of the editor. Tabs restored from storage or opened from History count as configured, so a reload never drops you back into the picker. - The launcher preselects the preset the last query started from, marks it "Last used", and applies it in one click. Locally-saved presets carry rename / delete, and the custom path can save the current setup as one. - The custom path embeds the existing settings form with the tab's bindings and adds a footer: back to presets / save as preset / start query. - applyPresetToTab() replaces the tab-creation-only preset path, so a preset can also reconfigure an open tab. presetMatches() moves to query/preset_match.ts, shared by the form and the launcher. - A preset no longer touches WHERE the traces come from: a catalog shared across users can't know that, and clearing it left the trace grid with no directory. Trace-source settings stick unless a preset names them, and a fresh tab inherits them from the last configured query (lastSetupState). - The chip strip gains "Change setup" (back to the launcher, settings kept) and drops the trace-cap chip, which now has a toolbar control.
Three routed pages (home, query, settings) plus a left nav, for an app that only ever does one thing: run queries. Presets moved into the tab launcher and per-query settings into the tab itself, so nothing is left for the other two pages to hold. Delete them. - index.ts mounts QueryPage directly: no Gate, no route resolution, no hash router (router.ts / routes.ts / home_page.ts / layout/sidebar.ts and the cross-page queryState signals are gone), and no left-sidebar command. - The topbar becomes the only chrome: brand, omnibox, and on the right the new connection control — the endpoint is a connection, not a query setting, so it's the one thing that stays global. It shows the backend host, opens a panel to edit it (with reload-to-apply and the config load error), and auto-opens when nothing is configured, replacing the old home-page "connect a backend" onboarding. - The `auto` sidebar column in the shared ui_main grid collapses to zero with no sidebar element, so no shared style needed changing.
The settings page was built to work two ways: standalone (writing global state) and embedded with per-tab bindings. The standalone half died with the routes, and with it the idea of global query settings — a query is now configured by a preset or by its own custom setup. - settings_page.ts becomes query_settings_form.ts / QuerySettingsForm, with `bindings` required. Gone: the SettingsShell chrome and search box, the endpoint card (it lives in the topbar now), and the preset picker (the launcher owns choosing a preset). Every read/write goes straight through the bindings, so ~40 lines of "per-tab or global?" branching go too. - The card styles ship with the SettingsShell widget, so the form imports that stylesheet directly rather than depending on the shell being mounted. - trace_selection_state keeps only the trace grid's shown-columns preference — a display choice that isn't part of a query. The filter, processing order and result-metadata columns were global only to seed new tabs; they're now per-tab, restored from a tab's own snapshot or set by the launcher. - Starting a query from the custom path remembers the preset it matches (a setup just saved as one), so the next tab preselects it.
The launcher commit carved out the trace-source settings: a preset that didn't name them left whatever the tab already had, on the theory that a shared catalog can't know where a user's traces live. That is the UI deciding for the user. Drop the carve-out: applying a preset configures every registered setting, and one the preset omits is turned off like any other. A run that then has no trace source says so, and the user sets it. Fresh tabs stop inheriting the last query's trace source for the same reason. The launcher's custom path still seeds its form from the last setup — that's a starting value the user sees and can change before starting the query, not a decision made behind their back.
Nobody asked for it. The custom path pre-filled its form from whatever the last query ran with, which is another decision made on the user's behalf and one more piece of state to reason about. The form now opens on the backend defaults, same as the settings it shows. Removes lastSetupState / QuerySetup / setupFromTab entirely; the state module keeps only the last-used preset id (which the user did ask for, to preselect a preset on a new query) and is renamed to settings/last_preset_state.ts.
All / Ephemeral / Persistent encoded a two-value filter as a three-way mode,
where "All" was just both kinds at once — a third of a narrow sidebar row
spent on derivable state, in a control that still read like the tab strip it
replaced. Two independent checkboxes say what they do.
Unticking both is allowed and shows an empty list saying so, rather than
silently re-ticking one or quietly showing everything.
The store's filter becomes {ephemeral, persistent}; filterHistory() keeps an
entry when the box for its kind is ticked. The segmented control stays, still
used by the launcher's preset groups.
Applying a preset turns off every setting it doesn't name, and the trace cap is a setting — so any preset from the reference catalog (which declares only treat_trace_errors_as_warning) landed the cap in disabledSettings. effectiveTabSettings drops disabled ids, so the run shipped no trace_limit at all and fanned out over the whole corpus, while the toolbar read "Traces: 100000". Typing a new cap didn't help either: setTraceLimit wrote to querySettings, the input re-rendered with the typed value, and the disable still won. The cap isn't really a preset-scoped setting — it's a run control with a per-mode default, like the row limit. So a preset that doesn't state one now gets the default for its mode written explicitly, instead of being disabled. A preset that does state one still wins. Two supporting fixes so the controls can't lie again: - setTraceLimit clears the disable, because the toolbar control IS the cap. - The toolbar hides the Traces box, and the settings card reports no value, when the tab has the cap switched off — an uncapped run now looks uncapped. Found by review; regression tests cover the omitted-cap, stated-cap and re-enable paths.
Four things review turned up around the new surfaces:
- The connection button called itself connected whenever an endpoint was set,
but the endpoint ships with a default — so a user who has never configured
anything saw "cloud_done" and the corp host, and the auto-open onboarding
never fired. Connected now means the backend answered
(execConfigLoadError === undefined), the button says what it isn't
connected to, and the panel opens itself once when there's no working
backend.
- The launcher flashed "No presets yet" for the length of the /trace_presets
round trip. It now shows a loading state, and when the catalog really is
unreachable it says so and points at the connection button — the deleted
home page had this guard and the port lost it.
- An uncategorised preset ("" is a valid category) never got its group
preselected, because the launcher looked up the raw category while
groupPresetsByCuj buckets it as "Other". The badge sat in a group the user
had to go find.
- "Change setup" is disabled mid-run: it swaps the editor, status box and
Cancel button for the launcher, where picking a preset would rewrite the
tab under a query that's still executing.
Also retargets the empty-endpoint run error, which still sent people to a
Settings page that no longer exists.
Sweeping up after the revamp. ~230 lines of CSS that nothing renders any
more:
- .pf-bt-simple-table-list, orphaned with the stdlib schema browser;
- .pf-bt-preset-chip{,s} and the .pf-bt-preset-picker spacing rule, orphaned
when the settings form lost its preset picker;
- the "Query" CTA rule in the Traces card header, orphaned with the button.
The endpoint, chip-modal and settings toggle/warning/hint/category-header
rules go too; those were already dead before this branch.
Also: comments naming the settings page, SettingsPage and traceOrderByState
now describe what's actually there, and four helpers that only their own file
uses (newLocalPresetId, modeDefaults, clampTraceLimit, explicitTraceLimit)
stop being exported.
There was no way to fork a query: you either edited a tab in place and lost what produced its results, or saved the setup as a preset and applied it somewhere else. "Duplicate" on the chip strip opens a copy in a new tab — same SQL, settings, filters, metadata columns, order-by, mode and caps. The copy carries no queryUuid, so it's unrun: running it creates its own execution instead of adopting the original's, and reopening either from History still lands on the right tab. Titles go "<name> copy", "copy 2", … so repeated forks stay apart.
It sat in the wrapping chip row with Settings and Change setup, which read as if it configured the run. It doesn't — it acts on the tab as a whole — so pin it to the right edge, away from the configuration chips, where it also can't be pushed around by a long filter row.
Renames the chip, QueryTabsState.cloneTab, and the title a clone gets
("<name> clone", "clone 2", …) so the button, the API and the tab title all
say the same word.
Missed when the file was added; presubmit's prettier check caught it.
The loading and error branches returned a bare EmptyState, skipping the .pf-query-history wrapper and the caller's class. The panel lost its height:100% flex column and sprawled — a tiny message adrift in a white half-screen — and the toolbar went with it, so the refresh button that retries the failed load wasn't there to click. The frame is now unconditional: the kind filter and refresh always render, and loading / error / empty all live inside the list area.
The CSP pinned style-src to two sha256 hashes. CodeMirror generates its stylesheet at runtime, so no fixed hash can cover it: the browser refused it and the query editor rendered unstyled — white background, gutter stacked above the code, no syntax colouring — in any browser that enforces the policy. The hashes have been there since Bigtrace UI V0 and have drifted since. Allow 'unsafe-inline' for styles, the same allowance ui/src/frontend makes for the same reason.
"Change setup" swaps the editor for the launcher, and the only ways out were picking a preset — which overwrites the tab's SQL and settings — or going deeper into custom settings and pressing "Start query". Changing your mind meant either losing work or guessing. A back control now sits above the title, first in reading order: it restores the editor and changes nothing. It appears only when the tab has something to go back to (a query, a run, or results), so a brand-new tab isn't offered a way back to an empty editor. That test reads the tab's own contents, so it still holds after a reload.
It copied the executed SQL to the clipboard, from a toolbar sitting directly below an editor showing that same SQL. Clone covers reusing a query properly, and the row it lived on is better spent on the result controls. The one thing it did that nothing else does is hand back the SQL a run actually used after the editor has been typed over — but that's the "what did this run with?" question, which wants a snapshot of the whole submitted configuration, not a copy button for one field of it.
The settings form rendered two ways: inline in the launcher, and inside a
modal opened from the chip strip. Same content, two containers, and two chips
("Settings", "Change setup") that differed only in which half of that content
they landed on.
Now there is one surface — the launcher, in the tab — and one button. Settings
opens it on the settings form; the preset gallery is a click away inside it,
where "Back to presets" already lived. Which half it shows is state on the
tab, so both entry points and the surface's own navigation share it.
Falling out of that:
- The modal opener is gone.
- The primary action reads "Done" for a tab that already has a query, and
"Start query" only when there isn't one to go back to.
- "Back to presets" hides mid-run, because applying a preset would rewrite a
tab whose query is still executing. Editing settings for the next run stays
available, which the modal used to allow and the old chip did not.
Presets belong to starting a query; settings belong to one that exists. From a tab that already has a query, clicking a preset isn't configuring it — it replaces the SQL, title, settings, filters, columns and caps wholesale, with no undo, one click inside a button labelled Settings. Opening a new tab, or Clone, does that job without costing the work in front of you. So the surface now offers exactly one way back, and which one depends on whether the tab has something to go back to: a new tab gets "Back to presets", an existing query gets "Back to query". The overwrite hazard is simply unreachable from a working tab.
The settings form writes every edit straight onto the tab, so the two ways out of it — "Back to query" and "Done" — did the same thing. Placed side by side they read as cancel and commit, and someone who tried a filter and pressed Back to undo it found the filter kept. Back now means what it says. Opening Settings on a tab that has a query keeps a snapshot of the run configuration (settings, on/off state, trace filters, metadata columns, order, caps, mode); Back restores it, Done drops it. The snapshot lives on the tab as view state — the tab stays configured while the form is up, so a reload closes Settings with the edits kept, as Done would. A new tab configuring its first query has no such session: nothing to go back to.
The preset card renders a description, but a preset saved from the UI never had one: the save dialog asked for a name and a group, and the only edit afterwards was a rename. So a user's own presets sat in the gallery as bare titles next to the catalog's described ones. The save dialog gains a Description field, and the card's rename becomes an edit of name, group and description together — one form behind both. The name is checked as it's typed: a name the catalog already uses is refused outright, since a local twin couldn't be told apart in the gallery; a name the user already has still offers to overwrite when saving, and is refused when editing another preset into it. A preset keeps its own name while being edited, even one that already shadows the catalog.
The launcher's primary button doubles as "Start query" on a new tab and "Done" on one that already has a query, and both ran the same handler: work out whether the tab's setup matches a preset and record that (or nothing) as the preset to preselect next time. That is right for starting a query by hand. For Done it meant that opening Settings on any tab and changing an option cleared the "last used" badge for the next new tab, though nothing had been started. Only Start query records it now.
"Back to query" was named when it was pure navigation; now that it puts the configuration back to what it was, the label undersold it and left the discarding to a tooltip. "Done" beside it read as its opposite without saying so either. Cancel and Apply say what happens. Apply rather than Save: "Save as preset…" sits in the same footer with a Save button in its dialog, and nothing here is saved anywhere a user would look — the settings simply become the query's, which is what Apply means elsewhere in the UI (DataGrid filter menu, Data Explorer nodes). The new-tab footer is unchanged: "Back to presets" is a step back that keeps what was typed, and "Start query" is not an edit.
The settings form chose its footer by whether the tab had SQL, a run or results. A configured tab with none of those — Start query pressed on an empty form — then got the new-tab footer from the Settings chip: "Back to presets" and "Start query" instead of Cancel and Apply. Worse, Back to presets led to the gallery with the Settings session still open, and picking a preset there applied it without ending the session, so the editor never came back. The footer now follows the session: opened from Settings on a configured tab it is Cancel / Apply, whatever the tab holds; only a new tab's form offers Back to presets. Applying a preset ends any session and leaves the launcher outright, so no path through the gallery can strand a tab there.
Presets left the Settings surface when applying one meant replacing the tab wholesale. But a preset need not carry a query at all — a setup-only preset is a trace source, a selection, options, caps and a mode — and "run my query with that setup" is a real thing to want from a query that already exists. So the settings form gets a picker at the top, "Settings from preset". From there a preset lends its setup only: settings (what it doesn't name is off, as ever), trace selection, result columns, order, caps and mode. Never the SQL, never the title — Settings is where you decide how a query runs, not what it runs; starting a query from a preset, SQL and all, stays in the launcher gallery. Like every edit in the form it is provisional: Cancel undoes it, Apply keeps it. The picker doubles as an indicator. It reads the preset whose setup the tab runs with right now, strictly — named settings at their values, everything else off, the trace cap aside — and "Custom" once nothing fits. Query-only catalog presets share one empty setup, so the one last picked is read back for as long as the setup still is that setup. Since a preset's setup includes the row cap and the mode, the form now shows them too (a "Run" section: Persistent, Row limit), so nothing the picker touches is off-screen. Gallery cards without a query say "Setup only", and applyPresetToTab is split so the gallery applies setup + query while the form applies setup alone. Two binding members from the modal era that nothing called (getSql, setQueryAndTitle) go; the run controls take their place.
The catalog's query-only presets all share one empty setup, so a tab started from "OOM kills" read back in the settings form as "Settings from preset: LMK events" — the first twin. The picker preferred the preset picked in the form, but only for the life of that form; a preset applied from the gallery, or anything after a reload, had no such memory. The tab now records the preset last applied to it, from either place, and persists it with the rest of the tab (clones inherit it). The picker prefers that one for as long as the setup still is that setup.
The "Settings from preset" dropdown was the wrong shape: a select is a
value, not a set of things to look at, and it hid the cards — descriptions,
groups, the setup-only tag, the edit and delete tools — behind names.
The settings form now carries the launcher's own gallery, folded under a
"Presets" heading in the section-title style; open it and the cards are
there, grouped as in the launcher. What picking one does is unchanged from
the dropdown: the preset lends its setup — settings, trace selection, result
columns, order, caps and mode — and the query stays the user's, provisional
until Apply. The card whose setup the tab runs with is marked "Current", and
the folded heading says the same ("Setup from X", or "Custom setup"), so the
answer is there without opening it.
To share the cards between the two places, the gallery becomes its own
component (PresetGallery: grouping, cards, local edit / delete) and the
save / edit / delete dialogs move to preset_dialogs.ts; the launcher renders
the gallery for starting a query and passes the folded one to the form as a
header slot. isLocalPreset lives with the local store now.
"Pick a preset to use its settings here. The query is not changed." — the list of what a setup contains belongs in the docs, not in the form.
Settings and Clone were chips — display elements with no hover state and, in Clone's case, not even a pointer cursor — sitting among chips that really are display. They are the two actions on that row, so they are buttons now: compact, filled, with the hover and press states that come with that. Cancel in the settings footer was a bare text button next to a filled Apply, easy to miss for the one control that undoes everything. It is filled like a dialog's Cancel, and so is Back to presets on a new tab, so the way out is as visible as the way forward.
The folded Presets section was rendered for both ways into the settings form. On a new tab that made no sense: the gallery is one step back behind "Back to presets", and there a preset starts the query, SQL and all — while the same cards folded into the form would have lent their setup only. Two galleries, two meanings, one screen. The fold appears only when Settings was opened on a query that exists, which is the case it was made for.
Starting a query was two screens — the preset gallery, and the settings form behind "Configure custom settings" with "Back to presets" as the way back — while Settings on an existing query was a third arrangement of the same parts. Now there is one page with two sections, Presets and the settings form, and only which section is folded depends on the tab. A tab with no query yet: title, then Presets open (a card starts the query outright, whole preset), then "Custom settings" folded with the full form inside; the footer offers Save as preset… and Start query. Settings on a query that exists: Presets folded (a card lends its setup only), the form open, Cancel and Apply in the footer. Same page, same parts, no modes: setupMode, the two navigation buttons and the switching code are gone. The settings form takes a `fold` option for the folded case, and the launcher's styles collapse to the one layout — a scrolling body over a footer.
On a new tab a preset card applied the whole preset and opened the editor in one go; in Settings on an existing query a card lent its setup and waited for Apply. Two behaviours for the same cards on what is now the same page. Now a card only ever fills the page in. On a new tab that means the whole preset — query, title, setup — with the card marked "Selected" and the folded "Custom settings" heading reading "From X", so the setup can be opened and adjusted (a catalog preset's missing trace source, say) before Start query opens the editor. In Settings a card still lends its setup only, and Apply keeps it. One rule: cards fill, buttons commit. Reading the selection back is strict — the query and the setup must be exactly the preset's — so a fresh tab is nobody's and the last-used suggestion shows until a card is picked. Start query records the selected preset as last used. applyPresetToTab no longer configures the tab (that is Start query's job), and the unused addTabFromPreset goes.
It existed for one stale state — a tab persisted mid-Settings by an older build, with a query but no configuration flag — and keyed on "the tab has SQL". Now that a card fills a new tab's query in without leaving the launcher, that key fired on the ordinary path and offered a "Back to query" above the presets of a tab that had never had one. Nothing produces that stale state any more; the link and its predicate go.
A tab took the name of the preset it started from, the first line of its SQL on the first run, or "<name> clone" — three schemes, none asked for. Tabs now keep the default "Query N" like the main UI's query page, and change only when the user renames one; restored tabs keep whatever they were called. A clone is a new tab and is named like one. The SQL-derived title had a second job — telling an empty editor from one with something to run — which hasQueryText keeps.
One form carried everything a query runs with, so the launcher page — and a preset, and the Settings button — dealt in all of it at once. The feedback: a query's identity is its traces and its SQL; the rest is knobs. So the configuration splits by what it's about. The launcher page (a new tab, and the chip-strip button on an existing query, now labelled "Trace Selection") holds presets and trace selection only, flat — no accordions: the Presets section, then the trace grid leading the source settings and the result-metadata columns. The remaining settings — the trace cap and every non-trace category — move to a Settings modal behind a gear at the end of the run toolbar, with the same contract as the page: edits land on the tab as they're made, Apply keeps them, Cancel (or closing the dialog) restores the configuration from when it opened. The Traces box leaves the toolbar — the cap is edited in the modal — keeping the default surface to Persistent, Rows and the gear. The chip strip follows the same line: it summarises which traces the query runs over, so it chips only trace-selection settings (plus the grid filters, as before) — options aren't chipped, they live behind the gear. Presets shrink to match: a preset is a trace selection and a query — source settings, grid filter and order, result columns, SQL — and nothing else. Applying one no longer touches options, caps or mode (what a stale catalog entry still names outside that scope is ignored), the old "write a mode-default trace cap" special case disappears, saving a preset captures only its scope, and matching compares only its scope. Within that scope a preset is still the whole selection: trace settings it doesn't name are turned off.
The card gallery was a lot of chrome for a one-line choice. In its place, one row: the category selector as before, and beside it a dropdown holding that category's presets, with edit and delete alongside when the named preset is the user's own. Descriptions ride as tooltips — on each option while choosing, on the control once something is chosen — not as page text. Clicking a category applies one of its presets outright — the one last applied there while the page is up, else the tab's last preset when it belongs to the category, else the first (deterministic, never random) — and the dropdown refines the choice within it. So once the picker has been touched there is always a preset applied, and its value always names the one the page is actually filled from. Until then it reads "— choose a preset —", opens on the last-used preset's category and marks that preset "· last used" inside the list — a suggestion, not a choice; "· setup only" is marked the same way. Picking stays provisional as everywhere: the whole preset on a new tab, the trace selection on an existing query, the footer commits and Cancel restores. The PresetGallery component and the card styles go.
Two column pickers shared the page: "Shown columns" (a global display preference for the trace grid) and "Query Result Columns" (the per-tab trace_metadata_columns attached to result rows). Near-identical names, one distinction nobody should have to hold. Now there is one: the grid's shown columns ARE the metadata attached to every result row. The picker is backed by the tab's traceMetadataColumns — so presets capture it, applying a preset sets the grid view, Cancel/Apply covers it, and the snapshot reports it — and the global display preference (traceColumnsState) is deleted. Emptying the set falls back to the backend's defaults: the grid needs a column, and "attach nothing" isn't a state the page offers. The page also stops narrating itself. The "Presets" and "Trace selection" section headings go, replaced by one heading for the whole activity — "Select a trace corpus", with "Start from a preset for your vertical, then filter the table down to the traces of interest" beneath. The grid card drops its "Traces" title and its filter-or-sort caption, and the refresh button moves into the grid toolbar, left of the trace count.
A backend may declare a trace_uuids setting: a second, mutually exclusive way to select the corpus — instead of a source plus the grid filter, an explicit list of traces. Showing it as an ordinary settings card would miss the point; when the list is the selection, nothing else on the page applies. So it renders as a mode, not a card. When the config declares the setting, a "Paste trace UUIDs…" entry joins the corpus page's picker row; taking it swaps the grid and the source cards for a single paste box — commas, spaces or newlines, deduped, with a live count — and "Back to filtering" swaps them back. The chip strip summarises it as "Trace UUIDs: N" and shows none of the filter-mode chips the list overrides. The mode is derived, not stored: the tab is in it exactly when it holds its own enabled trace_uuids entry. Requiring the explicit entry keeps a fresh tab out of the mode even though the config (which declares the setting) arrives only after the first tab is created — the tab-creation mirror of globally-disabled settings can't cover settings it hasn't seen yet. Because it's ordinary settings plumbing beyond that, everything follows for free: clones carry the list, Cancel restores it, Apply keeps it, the submit-time snapshot reports it, saving a preset captures it, and applying a preset that doesn't name it turns the mode off. Toggling is non-destructive both ways — the filter-mode configuration is hidden while the mode is on, and the list survives leaving and re-entering it. Wire contract (documented): enabled + non-empty trace_uuids means the backend selects exactly those traces and ignores the source settings, the trace_filters / trace_order_by fields and trace_metadata_columns; an empty list reads as absent. Backends declare it stringArray, disabled by default.
The backend promoted trace_limit: it travels as a top-level field on /execute_* now, like the row limit, and no longer appears in /bigtrace_execution_config. That deletes a whole apparatus the UI had built around it being a setting: the derived per-mode value injected into the settings payload, the clamp against declared min/max, the enable/disable state meaning "uncapped", and the fresh-tab replacement of the backend's default. In its place, a plain number on the tab — tab.traceLimit — handled exactly like tab.limit: defaulted per execution mode (10k ephemeral / 100k persistent), moved with the mode while untouched, persisted with the tab, copied by Clone, covered by the settings snapshot so the gear modal's Cancel/Apply polices it, shipped as trace_limit on every run, and restored from the traceLimit snapshot field when a query is reopened from History. The gear modal's Trace Limit card becomes a plain number input (no toggle, no reset-to-declared-default), and the gear is always present — the cap is a universal request field. The trace_limit setting id disappears from the UI entirely: with one backend, already migrated, there is nothing to be defensive about.
Corpus-page picker: the native select becomes a PopupMenu — an outlined trigger button (steady width, name + arrow) opening app-rendered rows with a check on the applied preset and descriptions as tooltips. A muted middot separates the category strip from the trigger, and the "Paste trace UUIDs…" entry anchors to the row's right edge with an auto margin, so it holds even when the row wraps on narrow widths. Preset management moves out of the picker into a Manage presets… button in the launcher footer beside Save as preset… — reachable in either selection mode (the dropdown was unreachable exactly in UUID mode, where the picker hides). The modal edits and deletes in place: a row expands into a contained editor (name/group/description, the save dialog's conflict validation) or swaps its actions for a Delete/Cancel confirm — no dialog ever replaces the modal. The editLocalPreset and deleteLocalPreset dialogs and the applied-preset-only inline buttons go away; managing a preset no longer requires applying it first. Toolbar and labels: Format moves next to the Run cluster (divider added there, the orphaned one before Persistent removed); the gear modal is titled Advanced Query Settings; the corpus grid's column picker is named Query result columns; the "setup only" marker is dropped from preset labels and the manage list.
The backend can now fan a query out over one arm of an experiment. This puts that in the user's hands and carries it everywhere the rest of the trace selection already goes. The trace grid's header gains an Experiment Filter. Searching asks /v1/experiments (one capped page, debounced, never for an empty box — matching the whole catalog is a round-trip nobody can use), and each result offers both arms: id first, then the name, which routinely runs past a hundred characters and so is clamped everywhere with the whole of it in the tooltip. An arm the caller can't query is prefixed "(Not available in Telemetry datasets)" and stays selectable — the backend denies at execution, not the UI. Picking one replaces the button with the arm toggle, the chosen arm, and a way to clear it; switching arm or clearing refetches the grid, so what it lists stays what a run would process. From there it travels as any other part of the selection: shipped as the top-level experiment_filter on /execute_* and /trace_metadata, persisted with the tab, copied by Clone, restored from the submit-time snapshot when a query is reopened from History, captured by and applied from a preset (which a preset states as ids and arm alone), compared when the launcher works out which preset a tab is running, held provisional by Trace Selection's Cancel/Apply, and summarised in the chip strip. Ids are numbers on the wire in both directions and stay numbers throughout, so there is no conversion to keep in step; display names never travel — they are resolved from /v1/experiment_metadata once per id, and only onto the filter that asked for them.
gignat-dev
force-pushed
the
dev/gignat/bigtrace_spa
branch
from
August 25, 2026 09:42
b2a7ee0 to
6ec15fd
Compare
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.
No description provided.