345 Commits

Author SHA1 Message Date
Will Fry
01d4659a3e GDGT-3241: SegmentedControl constrained text + icon props (#83336)
* GDGT-3241 Constrain SegmentedControl items to text and icon props

Items are now typed data (label, icon, ariaLabel) instead of arbitrary
label nodes, and the component owns icon size and spacing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update Loki Snapshots

* set line height on SegmentedControl label

* add second click to assert handler doesnt refire on multiple clicks

* interface -> type

* add test

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-10-06 15:42:18 +00:00
Fraser
145357fbb1 Skip unused addons and font loads in Loki screenshot runs (#83068) 2026-10-06 14:23:32 +13:00
Andrei Chernykh
c80438a092 Migrate the filter value lists off Emotion (#83384)
* Migrate the filter value lists off Emotion

* Update Loki Snapshots

* Address review feedback and truncate long filter values

* Revert the truncation of long filter values

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-10-02 13:48:33 -07:00
Denis Berezin
24a52962de UXW-3922 Add syntax highlight for AI system prompts (#79675)
* UXW-3922 Add syntax highlight for AI system prompts

* Review fixes

* Fix CI

* Update Loki Snapshots

* Update Loki Snapshots

* Fix e2e

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-10-02 22:18:58 +05:00
Stas Gavrylov
cbc6c3a858 Restyle Sidesheet to Figma and add the offset variant (GDGT-2480) (#82747)
* Restyle Sidesheet to Figma and add the offset variant (GDGT-2480)

Brings the common Sidesheet in line with the Figma spec and adds the
inset ("offset") variant the design system defines.

- offset?: 16 applies a 16px top/right/bottom inset and a 16px radius;
  typed as the literal 16 so unsupported values can't enter the API
- 464px (29rem) default width, applied only when `size` is omitted so
  the named sizes stay exactly as they were
- surface-primary background, xs_outline elevation, 24/24/16 header,
  h4 title, and a 32px close control with a 16px icon
- Storybook: added an Offset story, and reduced the MDX page to a
  summary. Each story embedded there rendered a real fixed-position
  Sidesheet, so the docs page stacked them all on top of each other
  and none could be read; the stories remain in the sidebar.

Mantine Modal internals (focus trap, escape handling, overlay, portals,
nested subpages) are unchanged; this is a restyle plus one additive prop.

* Use the design-approved sm width for Sidesheets

* Make Sidesheet offset a boolean option

* Move static Sidesheet styling to Mantine props

* Add Loki references for Sidesheet layouts

* Update Loki Snapshots

* Wait for Sidesheet before capturing Loki references

* GDGT-2480: Address review comments

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-10-01 14:00:15 +03:00
Will Fry
121fa7febe GDGT-3240: SegmentedControl (#82657)
* GDGT-2463 update SegmentedControl to new Figma designs

* Update Loki Snapshots

* Update Loki Snapshots

* Update Loki Snapshots

* exclude `c` prop from SegmentedControl

* remove disabled attribute from segmented control item

* Show SegmentedControl overview story in light and dark themes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:09:42 +00:00
Anna Tomka
97283f9c02 GDGT-3231: 1. Combobox: add Select, MultiSelect and Combobox stories with Loki snapshots (#82756)
* test(ui): add Select, MultiSelect and Combobox Overview stories with Loki

* test(ui): address review, drop Combobox story and searchable sections

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-29 10:47:45 +00:00
Andrei Chernykh
e28f66a5ac Migrate the visualization skeletons off Emotion (#82992)
* Migrate the visualization skeletons off Emotion

* Skeletons: shared svg wrapper, inlined caption, Loki stories
2026-09-28 11:06:51 -07:00
Anna Tomka
bc75a88888 GDGT-2458: Accordion (#82562)
* test(ui): add Accordion Storybook story and Loki filter

* feat(ui): restyle Accordion from Figma

* fix(Accordion): keep theme selectors one class deep so call-site overrides still win

* refactor: drop Accordion call-site overrides now covered by the theme

* test(ui): add Loki reference for Accordion Overview story

* fix(Accordion): hide the default chevron from assistive tech

* test: update DataComplexityCards snapshot for the Accordion separated default

* fix(RemoteSyncDependencyModal): drop overrides the new Accordion theme makes redundant, keep control transparent

* fix(RemoteSyncDependencyModal): let the Accordion theme space the rows

* fix(Accordion): keep icon grey on disabled hover, honour the radius prop

* test(ui): add disabled hover row to the Accordion Overview story

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-28 10:20:07 +00:00
Egor Grushin
fa20179ff6 Restyle Button to the Figma spec and migrate call sites (#82236) 2026-09-23 16:59:13 +02:00
Will Fry
b084ade539 GDGT-3224: migrate TabRow to CSS modules to fix gradient theme issue (#82482)
* GDGT: migrate TabRow to CSS modules to fix gradient theme issue

* Update Loki Snapshots

* increase scroll gradient size

* Update Loki Snapshots

* Add dark theme story for narrow dashboard with many tabs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Add placeholder Loki reference for dark theme narrow tabs story

Copy of the light theme snapshot so the loki-update workflow detects a
diff and replaces it with the CI-generated image.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Update Loki Snapshots

* move left/right styles into existing classes

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:01:26 +00:00
Nick Fitzpatrick
72ea707064 Add dataPointsVisible extension to eCharts (#81721)
* useRenderedDataVisibility

* static viz, other adjustments

* remove dead hook

* Split panels, simplification

* handling line series with a range very close to chart boundary, ignore goal line

* return true on traversal if intersection is found
2026-09-14 22:44:43 -03:00
Jakub Chodorowicz
236a1cc9bd Gdgt 2461 tabs (#82174) 2026-09-11 22:30:22 +00:00
Anderson
1a8883d604 Revert "Apply responsive spacing and typography to dashboard chart cards" (#82346)
* Revert "Apply responsive spacing and typography to dashboard chart cards (#81…"

This reverts commit 13e00d55b9.

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-11 13:10:36 -03:00
Anna Tomka
5b7c89d8ee GDGT-2478 Translate PasswordInput toggle label at call time (#82338)
* GDGT-2478 Translate PasswordInput toggle label at call time

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-11 15:26:10 +00:00
Anna Tomka
ca17b89463 GDGT-2478 PasswordInput (#82117)
* test(ui): add PasswordInput Storybook story

* test(ui): add PasswordInput to Loki as baseline for restyle

* feat(ui): restyle PasswordInput from Figma and add input-background token

* test: cover PasswordInput visibility toggle in unit and e2e tests

* fix(PasswordInput): use highlight border color in focused error mode by overriding Input styles

* Update Loki Snapshots

* test(ui): add Loki reference for PasswordInput Overview story

* fix(PasswordInput): address review feedback

- keep border width constant and use an inset ring for focus/error to avoid layout shift
- tie right padding to the toggle button size so text clears it in lg
- restrict size prop to sm | md | lg
- order color keys alphabetically
- explicit visibility assertion in the e2e spec

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-11 14:18:41 +02:00
Oleh K
6a035e234c Use JetBrains Mono as the code font (#82278)
* Use JetBrains Mono as the code font

Ship JetBrains Mono (regular + bold) and point the monospace font family
at it, replacing the hardcoded Monaco/Menlo stacks scattered across the
codebase.

- Add the woff2 files, @font-face declarations, and the type, fallback
  map, docs and backend test entries that keep the admin font picker in
  sync with the fonts directory.
- Route every monospace call site through
  --mb-default-monospace-font-family, and define that variable in the
  embedding SDK's variable set, where it was previously missing. With
  the variable always defined, the per-call-site `, monospace` fallback
  is redundant and is dropped.
- Inline the variable at the CodeMirror theme, which was the only
  consumer of monospaceFontFamily, and delete typography.ts.
- Set the Code component to 13px/22px per the design spec.

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-11 12:12:15 +02:00
Anderson
13e00d55b9 Apply responsive spacing and typography to dashboard chart cards (#81906)
* Apply responsive spacing and typography to dashboard chart cards (UXW-5181)

* Update Loki Snapshots

* Fix preview height regression and update e2e tests for dashcard size tiers

* Fix card heading typography and non-cartesian chart bottom padding

* Update Loki Snapshots

* Apply new styles only on cartesian charts

* Move changed styled components to css modules

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-09 23:51:25 -03:00
Jakub Chodorowicz
c0949b1d38 Gdgt 2465 popover (#81561) 2026-09-09 15:26:04 +02:00
Egor Grushin
979b344073 Fix light theme border-highlight token (#82157)
Fix light theme border-highlight: brand[30] -> brand[60] (GDGT-2484)
2026-09-09 11:30:55 +02:00
Stas Gavrylov
c7a5060ca8 GDGT-2462: Update Switch component (#81709)
* GDGT-2462: Update Switch component

* GDGT-2462: Add Switch Loki references

* Update Loki Snapshots

* Address PR findings

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-08 15:42:53 +03:00
Stas Gavrylov
0e7e969925 GDGT-2467: Curate Paper radius tokens (#81700)
* GDGT-2467: Restrict Paper padding/radius to curated token options

* Update Loki Snapshots

* GDGT-2467: Update entity picker width e2e expectation for Paper padding

* GDGT-2467: Keep Paper default padding at 0, add no-padding option to stories

* GDGT-2467: Keep all padding options in Paper stories, only curate radius

* Update Loki Snapshots

* GDGT-2467: Type PAPER_RADIUS_OPTIONS against RadiusScaleKey

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-07 15:07:41 +03:00
Stas Gavrylov
5885d30ad0 GDGT-2486 Add elevation, radius, and spacing scale variables (#80481)
* GDGT-2486 Add elevation, radius, and spacing scale variables

* GDGT-2486 Remap scale tokens in files merged from master

* Address review

Review fixes for the GDGT-2486 scale migration:

- Revert double-shifted tokens from the merge-remap pass: it re-applied
  the old-to-new mapping to 22 files already remapped by the first pass,
  shifting spacing values one extra step (e.g. xl 24px -> xxl 32px).
- Fix codemod escapes where old key names kept their name but changed px:
  Modal default padding (lg->xl, 24px), MultiSelect radius (sm->xs, 6px),
  MetricSearchInput radius-sm, ThemeEditor PreviewPanel radius-md,
  ColumnInput theme.spacing.xs->xxs, ChartTypeSettings Space h props,
  TableRowForCacheableItem icon placeholder w (md->lg, 16px),
  ResizeHandle/ResizableBoxHandle grip h (xs->xxs, 4px).
- Pin Group/Stack default gap to "lg" (16px): Mantine's default is
  "md", which is 12px in the new scale (was 16px in the old scale).
- Allowlist the runtime-defined --mantine-spacing-xxs/-xxl and
  --mantine-radius-xxs CSS variables in find-never-defined-css-variables
  (fixes fe-lint CI).
- Add theme.unit.spec.ts covering the spacing/radius/shadow scales,
  dark-mode shadow set, whitelabel independence, and a guard asserting
  every scale-based component default resolves to a defined key.
- Derive Card/Paper story argTypes options from the real scale.
- Augment MantineThemeSizesOverride so the custom scale tokens appear in
  editor autocomplete for p/m/gap/radius/shadow props.
- Update the emotion-migrate and metabase-ui-component-from-figma skill
  token tables to the new scale (spacing/radius values, shadow tokens,
  snap tables).

* Update Loki Snapshots

* Address review: fix h/w style props missed by the scale codemod

The codemod did not remap height/width style props, so every bare h=/w=
scale-key value silently changed px with the new scale. Remap each to the
px-preserving key (xs 4px->xxs, md 16px->lg, lg 24px->xl, xl 32px->xxl):

- PaneHeader breadcrumb row h xl->xxl (32px, restores data studio
  pane header height)
- BrowseDatabases logo boxes h/w xl->xxl (32px)
- DatabaseConnectionStringField rows h lg->xl (24px)
- DatabaseEngineList logo Center h/w lg->xl (24px)
- Metabot chat chevron flexes h/w md->lg (16px)
- MetricsFilterPill h lg->xl (24px)
- DatasetNotebook divider h xs->xxs (4px)
- DataReference NodeList icon w md->lg (16px)
- transforms-python ResizableBoxHandle grip h xs->xxs (4px)

Audited: no remaining bare h/w/maw/mah/miw/mih old-key props whose px
changed (verified line-identical against master for every occurrence).

* Address review: fix bdrs and responsive-object size tokens missed by the codemod

Two more escape classes from the scale codemod, found by a full sweep of
every touched-token form (string style props, responsive objects, CSS
vars, theme accessors) with each line compared against master:

- bdrs style prop (border-radius): remapped px-preservingly at 69 sites
  in 58 files (xs 4px->xxs, sm 6px->xs, md 8px->sm). The codemod remapped
  radius= but not its style-prop alias bdrs.
- Responsive object values (e.g. p={{ base: "md", sm: "xl" }}): remapped
  the token VALUES at 9 sites in 6 files (base/sm/md breakpoint keys are
  untouched; only their spacing values shifted: md 16->lg, xl 32->xxl,
  xs 4->xxs).

Sweep result after fixes: zero remaining line-identical old-key token
references to spacing xs/md/lg/xl, radius xs/sm/md, or shadow xs/md in
any form. Intentional survivors unchanged: radius/shadow/bdrs "lg"
(disclosed broken-key-now-16px class), spacing/radius sm where the
px value did not change.

Verified: tsc, eslint (changed files), metabase/ui jest suite.

* Address review: fix remaining token escape forms (object props, braces props, style objects)

Final codemod escape classes from the exhaustive token sweep, all
remapped px-preservingly:

- Object-literal / default-parameter prop values (15 sites / 8 files):
  ItemList picker-item default containerProps pb xs->xxs (4px, the
  reported gap), FormFooter pt lg->xl, SlackSetup pt lg->xl,
  EmbeddingSecretKeyWidget mb xs->xxs, CreateDashboardForm labelProps
  mb xs->xxs x2, databases styles mb lg->xl, NotebookDataPicker
  menuDropdownProps mt xl->xxl, Banner story py md->lg
- Braces-wrapped string props: CloudSMTPConnectionCard p lg->xl (24px)
  and gap md->lg (16px), SettingsLdapForm mt xs->xxs,
  DashboardLeaveConfirmationModal mb lg->xl
- style-object CSS properties: DatasetNotebook borderRadius xs->xxs
- Ternary token values inside responsive objects: SummarizeStep gap
  md->lg (16px)

Sweep now returns zero real misses across every checked form: string
style props (incl. h/w/bdrs), braces props, responsive objects with
ternaries, object-literal/default-param props, style-object CSS props,
CSS var() references, theme.* accessors. Remaining grep hits are
untouched scales (lh, fz, size, breakpoints) or px-identical sm values.

* Address review: preserve Mantine spacing defaults exposed by Loki

* Update Loki Snapshots

* Address review: fix remaining scale regressions and visual guards

* Address review: make Loki update capture new references

* Update Loki Snapshots

* Address review: keep only targeted Loki references

* Address review: assign ticket component elevations

* Update Loki Snapshots

* Address review: tighten MonthPicker header spacing

* Address review: refine elevation visual stories

* Update Loki Snapshots

* Address review: keep renamed Menu Loki story selected

* Update Loki Snapshots

* Address review: preserve inherited Mantine defaults

* Address review: allow stock Mantine shadow tokens

* Address review: replace token audit test with ESLint rule

* Address review: drop unrelated whitespace-only dev/src changes

* Address review: use rem() in radius scale and rem docs

* Update Loki Snapshots

* GDGT-2486: Address PR comments

* GDGT-2486: Adjust negative tokens

* GDGT-2486: Address review findings

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-09-02 17:21:33 +03:00
Denis Berezin
84223a0d4d Restyle Number and Trend dashboard cards (UXW-5128) (#81131)
* Restyle Number and Trend dashboard cards (UXW-5128)

* Adjust sizes and tooltips

* Adjust sizes, fix icon

* fix

* Fixes after merge

* Update Loki Snapshots

* Fix multiple comparisons

* Add link to card title, Implement query builder view

* Undo scalar dashcard ellipsis menu styling changes

* Design review fixes

* Fix e2e

* Fix e2e

* Review fixes

* Update Loki Snapshots

* fix

* Self review fixes

* Self review fixes

* Add a "Show comparison value" display setting to the Trend visualization (#81148)

* Add Show comparison value setting to the Trend visualization

* Address review comments

* Standardize ellipsis menu styling across all dashboard cards (#81405)

* Standardize dashboard card ellipsis menus

* Update Loki Snapshots

* Update Loki Snapshots

* Refine dashcard menu button props

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>

* Fix review comments

* Update Loki Snapshots

* Add more tests

* Add keyboard navigation

* Fix borders for dark theme

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
Co-authored-by: Anderson <anderson@metabase.com>
Co-authored-by: Anderson <ands.alves.nunes@gmail.com>
2026-09-02 01:11:13 +03:00
Egor Grushin
05300912fb Radio component redesign (#80372)
* Redesign Radio

* Add Radio stories to loki filter

* disallow radio labelPosition

* use StoryBoard in radio stories

* add new story's snapshots

* Radio.Card: border, leftSection, migrate call sites

- card border 0.5px border-neutral-strong, none when disabled
- children disallowed, leftSection slot for icons
- EditUserStrategyModal + DataSegregationStrategyPicker on label/description

* update loki snapshots

* Icon color prop instead of css module in DataSegregationStrategyPicker

* remove extra div

* make the indicator in the radio card optional

* fixed radio size, no size prop

* radio card body via mantine text/stack

* forwardRef radio card

* radio card border: highlight when selected

* update loki snapshots

* drop size="xs" from radio call sites

* fix card growing on select, interactive card stories, optional checkbox card indicator

* skip interactive card stories in loki, update card snapshots
2026-08-27 19:35:48 +01:00
Jakub Chodorowicz
c433278e06 Gdgt 2472 loader (#80586) 2026-08-27 11:53:29 +02:00
Oleh K
11be7cb3e7 Restyle NavLink with primary and secondary variants (#80640)
* Add primary/secondary NavLink variants and sibling components (GDGT-2469)

Restyle the design-system NavLink from Figma:

- Add `primary` (brand-fill selected) and `secondary` (subtle gray selected)
  variants, with Default/Hover/Pressed/Selected states.
- Add the `icon-{primary,secondary,disabled,brand}-inverse` color tokens.
- Add `NavLinkButton` and `NavLinkBadge` sibling components for the right
  section; both invert automatically on a selected `primary` item (no prop),
  the button also has its own hover/press and stops click propagation.
- Give NavLink a `leftIcon` prop so it renders and colors the leading icon
  itself instead of styling the section slot.
- Add a two-variant state-matrix Storybook showcase (Loki-covered).

* Migrate NavLink call sites to primary/secondary variants (GDGT-2469)

- Admin nav: `admin-nav` -> `secondary`
- Entity picker: current level -> `primary`, other levels -> `secondary`;
  search results -> `primary`
- Remove the now-unused `default`/`mb-light`/`admin-nav` variant CSS

* Revert NavLink `leftIcon` prop back to `leftSection` (GDGT-2469)

Drop the polymorphic NavLink wrapper and colour the leading icon via the
`.NavLinkSection[data-position="left"]` CSS overrides again.

* Update Loki Snapshots

* Cleanup

* Update Loki Snapshots

* Add Loki reference for the NavLink Overview story (GDGT-2469)

* Update Loki Snapshots

* Address NavLink review comments (GDGT-2469)

- Migrate the remaining `default` NavLink (embedding SimpleDataPicker) to
  `primary`, restoring its selected styling
- Render NavLinkButton as a `<div role="button">` (valid inside a NavLink's
  `<a>`) with keyboard activation, instead of a `<button>`

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-08-27 11:42:14 +02:00
Denis Berezin
7692d3f479 UXW-5117 Fix Timeline event pills displayed on a wrong date when merged (#80567)
* UXW-5117 Implement timeline events stacks

* Improve performance

* Fix edge cases, improve performance, fix issues

* Review fixes

* Place chips at real data point

* Self review
2026-08-27 03:05:06 +03:00
Egor Grushin
27bfc0d005 Checkbox component redesign (#80262)
* checkbox redesign

* small fix

* fix disabled checkbox visual size

* disallow checkbox labelPosition

* add new loki tests

* update loki snapshots

* fixup! update loki snapshots

* fix e2e specs relying on checkbox Icon-check/Icon-dash classes

* checkbox card border

add border-highlight token, use it for selected card

* disallow checkbox size

single 16px size; xs/sm were both 16px and md was unused

* disallow checkbox card children

* drop redundant role from checkbox icon

* checkbox card body via mantine text/stack

* forwardRef checkbox card

* drop border on disabled checkbox card

* drop disabled checkbox border ring

alpha border over alpha bg stacked into a visible ring

* hide unchecked card indicator icon

mantine hides indicator icon via color:transparent; .icon color override defeated it

* update colors loki snapshot
2026-08-25 16:23:38 +01:00
Stas Gavrylov
a90f1170ba GDGT-2460 Update Toast styles (#80327)
* GDGT-2460 Update Toast styles

* GDGT-2460 Port Toast to Mantine Notification

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-08-24 16:46:10 +03:00
Kevin Kelleher
ce55f0cdc6 Fix axis labels for timezone naive strings (#80121)
* fix axis labels for timezone naive strings

* Update Loki Snapshots

* fix lint after rebase

* don't throw in tests

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-08-20 17:11:25 -04:00
Nick Fitzpatrick
97ecbeab61 Update auto 'Show Dots' logic (#80071)
* Update auto 'Show Dots' logic

* Update Loki Snapshots

* PR feedback

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-08-19 16:09:30 -03:00
Stas Gavrylov
0c5d3b3a59 GDGT-2459: Update Tooltip component (#79994)
* GDGT-2459 Update Tooltip styles

* Update Loki Snapshots

* Add Tooltip Overview Loki reference

* GDGT-2459 Add Tooltip spacing and shadow

* GDGT-2459 Use standard small Tooltip shadow

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-08-19 20:19:40 +03:00
Anderson
e406a40c47 Reveal column descriptions on hover in the Detail visualization (#78875)
* Show column descriptions in the Detail visualization via an info icon tooltip

* Reveal the Detail column description icon on hover instead of always

* Reserve the description icon's width so hovering never shifts the Detail table

* Address review comments

* Remove delay showing info icon
2026-08-12 23:56:09 -03:00
Denis Berezin
2f3d32a2ba UXW-4983 disable download to pdf for empty dashboard tab (#79490)
* UXW-4983 disable download to pdf for empty dashboard tab

* Add tooltip

* Update Loki Snapshots

* Review fixes - hide button when dashboard is empty

* Update Loki Snapshots

* Fix e2e

* Review fix

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-08-11 00:45:44 +03:00
Kevin Kelleher
789d334847 Explorations (#73244)
* Fixed failing CI

* Add query timestamp, refactor error marker

* no-mbql-introspection pass

* Order temporal-axis exploration variants by date, not aggregation

When the row cap fires on a time series, aggregation-desc ordering keeps
the highest-count buckets scattered across the full range: gaps in the
x-axis, old spikes beating recent data. Temporal axes now order by the
temporal breakout desc (aggregation desc as tiebreak) before the limit,
so the most recent contiguous window survives.

- time-facet: order by the temporal breakout (second breakout) desc
- default: branch on dim type — temporal dims by recency, categorical
  dims keep aggregation desc
- per-value-time-series: gains the same date-desc cap (had none, so a
  single fine-grained series could blow query-caching-max-kb)
- all three also filter the NULL date bucket: it can't render on a time
  axis, and its position under ORDER BY DESC is warehouse-dependent
  (Postgres NULLs first, MySQL last), making cap survival
  non-deterministic otherwise
- temporal-pattern-day/hour unchanged (bounded, no cap interaction)

* Added debug info to interestingness calculations

* even less mbql introspection

* oops, delete .scratch

* Sidebar UI fixes - error marker, loading shimmer

* Interestingness >= 0.7

* Adjust prompts list animation

* Fix e2e

* Note that the LLM query planner is a POC, not used in production

* Fix searching for explorations

* Use llm-metabot-provider for everything

* Log interestingless calculation as info for now

* Log interestingless calculation back as debug

* Fix FE lint

* fix ai summary showing dirty while generating

* Use llm-metabot-provider

* collection breadcrumbs

* add back null values for temporal variants

* order by for temporal pattern variants

* fix top-n-other

* doh, fix temporal pattern queries

* fix name in viz header, don't show be error in tooltip

* sort numeric bins in heat map header, fix tests

* UXW-4483 Add matching colors for category + breakout charts (#75912)

* Fix CI

* Fix units, fix loki

* UXW-4522 Update last query run timestamp in background

* Fix loki

* Fix e2e

* Improve Explorations viz types (#75958)

* UXW-4483 Add matching colors for category + breakout charts

* improve viz types first pass

* add error boundary

* add row chart support for dimension_value_colors

* Fix typecheck

* fix tests

* better handling of segments with row fallback, fix null color bug

---------

Co-authored-by: Denis Berezin <denis.berezin@metabase.com>

* UXW-4402: Make explorations respect sandboxing/impersonation/db routing (#75572)

* Make explorations respect sandboxing/impersonation/db routing

* some self-review fixes

* more cleanup

* test fixes

* doh

* Fixing CI failures

* capitalize 'By' consistently

* Fixed CI failure

* add loki snapshots

* Review fix

* Fixes after merge

* Rework Explorations UX (#76300)

* comment on exploration viz first pass

* wip

* wip

* wip

* UX changes first pass finished

* code cleanup, fix, add tests

* address review comments

* Code cleanup

---------

Co-authored-by: Nathan Voxland <nathan@metabase.com>

* Refine explorations with metabot (#75326)

* teach metabot about new exploration ux

* refine explorations

* address self-cr

* pass over FE

* CR response

* fix test

* Add :model/Exploration to api + eid-translation model-imports

These modules reference :model/Exploration after merging the latest
explorations base; declare the imports to satisfy model boundaries.

---------

Co-authored-by: Kevin Kelleher <kevin.kelleher@metabase.com>

* Harden explorations runner:
- planner crash recovery
- Bound timeline scoring
- add metrics

* UX tweaks

* Additional logging of errors on ai summary, plus increased thinking budget

* Improved timeline interestingness calculation

* Handle sonnet 5 etc better

* Make explorations groups a real thing (#76643)

* Rename ExplorationThreadGroup model to ExplorationBlock

Explorations organize their queries in a hierarchy: an Exploration has
Threads (one per run), a Thread has Blocks, and a Block is one
metrics-by-dimensions grouping that renders as a single sidebar heading.
That persisted grouping entity was named ExplorationThreadGroup, and the
word "group" was overloaded across the backend and frontend. Rename the
entity to Block — the name the frontend already uses — so the larger
data-model changes that follow start from one clear name. No behavior
change.

Renames :model/ExplorationThreadGroup -> :model/ExplorationBlock and
table exploration_thread_group -> exploration_block.

- Append a renameTable changeset rather than editing the createTable in
  place: the original is already applied to running dev DBs, so an
  in-place edit would fail checksum validation and force a DB rebuild.
- Rename the model namespace/file; update all call sites, the require
  alias (:as block), and insert-blocks!.
- Update model registrations (resolution, serdes models, copy, entity-id
  test) and re-sort the entries.
- Refresh the model's docstrings to describe a "block".

The planner's :thread-groups ctx key and the read-side group-tree are
left untouched here; they belong to later read-side work.

* Persist exploration pages with stable ids reconciled across reruns

Within a Block, an Exploration's queries are organized into Pages: a Page
is the bundle of queries for one (metric, dimension, query_type). Until
now a Page existed only as a derived, content-addressed key computed at
read time, so anything that wanted to point at a Page had to reference a
string that changed whenever the grouping definition changed. This makes
a Page a real persisted row whose surrogate id is its identity, so
comments (and stars, coming next) can anchor to a Page and survive both a
rerun — where queries are deleted and regenerated — and a future
re-grouping.

Introduce ExplorationPage: the persisted (block, card, dimension,
query_type) page within a block.

- New exploration_page table + model, FK to exploration_block (cascade)
  and report_card. Registered in resolution, serdes (excluded), copy,
  and the entity-id test.
- Add exploration_query.page_id (NOT NULL, FK, cascade): the reconciler
  stamps it on every query, so it's required from the start. group_id
  stays for now as the block pointer the read-side and deep-link URLs
  use; dropping it waits for the read-side rework.
- Reconcile in insert-plan-rows!, the single insert point for both
  initial Start and rerun: find-or-create a page per distinct key
  (stamping page_id), then GC pages left with no queries. reset-thread-
  for-rerun! is unchanged — pages survive a query delete (cascade is
  page->query), so find-or-create recovers the same page ids on rerun.
- No DB unique index on the key: dimension_id is text (not portably
  indexable on MySQL) and planning is single-threaded, so uniqueness is
  enforced by find-or-create and matching is done in Clojure.
- Migration changesets appended (the table/group_id changesets are
  already applied to the running dev db).

Tests cover page-id stamping, rerun id-stability, orphan GC, and perms.

* Return nested blocks→pages from the read API; drop query.group_id

An Exploration Thread's structure is Blocks, each containing its Pages,
each Page bundling the ids of its queries. Until now the backend instead
returned a hand-rolled flat list of typed nodes (sidebar/singleton/page)
linked by parent_group_id and keyed by auto:<...> strings, which the
frontend then re-nested. Now that Blocks and Pages are real rows, return
the nested shape the tables already encode: GET /:id returns :blocks,
each with its :pages, built from ExplorationBlock + ExplorationPage rows
and queries grouped by page_id.

- New metabase.explorations.blocks (renamed from .groups): blocks-tree
  builds the nested shape; block headings + page names + score-sorting
  ported over; block-display-name made public for the LLM planner ctx.
- Deep links go to a page by id: page-url → /question/research/<exp>/
  page/<page-id>; the AI-summary and document-append builders pass the
  primary query's page_id.
- Drop exploration_query.group_id (and its FK/index); a query reaches its
  block through its page now — build-row-context (used by the runner to
  finalize each row's MBQL) resolves the block via the page, which is what
  the group_id read there used to do.
- API schemas: ExplorationQueryGroup → ExplorationBlockNode +
  ExplorationPageNode.
- Migration changesets appended (the table is live on the dev db).
- Rewrite the read-model + endpoint tests for the nested shape; every
  ExplorationQuery test fixture now hangs off a page (page_id NOT NULL).

The create payload key stays :groups for now (separate input contract,
touches the data-picker flow). FE consumption of :blocks lands next.

* Consume nested blocks→pages on the frontend

The frontend renders an Exploration Thread as sidebar headings — one per
Block — with a navigable item per Page (a Page being the bundle of
queries for one metric/dimension/variant). It used to receive a flat
typed-node `groups` list and re-nest it itself; this switches it to the
nested `blocks` array (block → pages) the read API now returns, and
renames the navigable unit from "group" to "page" throughout. Users see
the same sidebar and charts — this lines the frontend up with the
backend's shape.

- exploration.ts: add ExplorationBlockNode / ExplorationPageNode; thread
  `.groups` → `.blocks`; remove ExplorationQueryGroup / its display-type;
  the navigable id is now a page node id (kept as an opaque string).
- Sidebar utils build the tree directly from blocks→pages (one heading
  per block, one child per page); score-sorting + empty-block filtering
  preserved.
- The navigable unit is renamed group→page end to end: the route segment
  becomes /page/<id>, matching the backend's AI-summary deep links. The
  selection/url/comment-draft id stays a string via String(page.id), so
  the existing string-keyed plumbing is unchanged.
- Visualization toolbar/header/comment-draft props renamed group→page;
  the mark-as-interesting handler is left a stub for the stars feature.
- Specs, stories, and test-utils rewritten to the nested shape.

The create-payload request key stays `groups` (separate input contract).

* Retain comment-anchored pages across rerun GC; point comment URLs at /page/

A Page bundles the queries for one (metric, dimension, query_type), and a
comment can be anchored to a Page — comment.child_target_id holds the page
id. On a rerun an Exploration's queries are deleted and regenerated, and
any Page left with no queries is garbage-collected. A Page a comment
points at must survive that cleanup even when it ends up empty, or the
comment would resolve to nothing. This makes the rerun GC comment-aware
and moves comment deep-links onto the page route.

- gc-orphan-pages! now retains an orphan page when some "exploration"-
  targeted comment's child_target_id equals the page id (compared as
  strings). Empty + no comment is still GC'd as before. Page ids are
  globally-unique PKs, so matching on child_target_id alone is
  unambiguous.
- exploration-comment-url: /group/<child> → /page/<child>, matching the
  page route and blocks/page-url; stale "group ID" docstring fixed.
- explorations imports :model/Comment for the retention lookup (comments
  doesn't depend on explorations, so no dependency cycle).
- No backfill: the dev Postgres has zero exploration comments and zero
  auto:* child_target_id rows, so the optional migration is skipped.
- Test: comment on a page → drop that page's selection → rerun → the page
  is retained and the comment still resolves; a no-comment empty page in
  the same rerun is still GC'd.

* Generate short and long page names from metric, dimension, and variant

A Page — the bundle of queries for one (metric, dimension, query_type) —
is shown in the sidebar under its Block's heading, but is also referenced
on its own elsewhere (comment deep-links, AI-summary chart titles,
search). Page names are derived, not stored, so a Page needs both a short
label that omits whatever its Block heading already shows (e.g. under a
"Number of Orders" heading: "By Category over time") and a full,
self-describing label for context-free places ("Number of Orders by
Category over time"). This generates both from the Page's parts — the
metric name, the dimension name, and a short per-variant qualifier —
rather than by string-slicing a finished name.

- variants/variant-qualifier: query_type → a short, param-free localized
  qualifier ("over time", "(Day of week)", ...) or nil for the default
  breakdown. Lives next to query-name so all variant naming is in one
  place. Unit-tested.
- blocks: page-long-name and page-short-name generate from parts (metric
  + dimension + qualifier) using tru templates and the block's anchor;
  page-node emits :name and :long_name.
- API ::ExplorationPageNode and the FE ExplorationPageNode type gain
  :long_name (FE display wiring left for separate work).
- Same-dimension pages that differ only by variant (default vs
  top-n-other) are now distinctly named instead of colliding; a
  blocks-tree test that asserted the collision now asserts they differ.

* Add ability to star pages, sidebar tabs UX (#76755)

* add ability to star, sidebar tabs

* - renamed "user interestingness" to "starred"
- Let starred just be true|false, no null
- Removed DELETE starred API, just PUT now

---------

Co-authored-by: Nathan Voxland <nathan@metabase.com>

* Fix CI: test compile error, migration boolean type, e2e formatting

- documents/search_test.clj: qualify text->prose-mirror-ast with the
  documents.test-util/ alias in the exploration-thread exclusion test.
  The unresolved symbol broke backend test compilation, which cascaded
  to every app-db/driver test job and clj-kondo ("0 tests" everywhere).
- 20260428_explorations.yaml: use ${boolean.type} instead of bare
  boolean for exploration_page.starred (required post-v49 so MySQL
  uses BIT(1)).
- oxfmt formatting for two explorations frontend files (fe-lint).

* Update e2e specs for reworked comment URL + explorations sidebar

- comment helper: match the node comment link by path only. The
  `?new=true` marker was removed from comment URLs (base commit
  "respect query params in all urls"); the CommentsMenu unit test was
  updated but this e2e helper was missed. Verified the comment flow
  still works (opening a node's thread shows the new-comment editor).
- explorations spec: align with the reworked sidebar —
  * `by <dim>` leaf label is now `By <dim>`
  * Scratchpad doc test: only Scratchpad is created synchronously; the
    AI Summary doc is created asynchronously, so stop asserting it (and
    the removed "Findings" grouping) at creation time.
  * AI-summary completion test: the AI Summary doc row now hangs under
    the thread heading ("Initial investigation"), not a "Findings" group.
  * remove the two "Add to document" tests — that control was removed.

* Fix stylelint rule-empty-line-before in explorations CSS modules

Add blank line before nested &:hover rules in ActionToolbar.module.css
and ExplorationGroupVisualization.module.css. These surfaced in fe-lint's
lint-css step once the oxfmt formatting failure was resolved.

* - cleaned up migration file
- Improve group renaming in code
- preserve starts between re-runs like comments

* Fixing CI failures

---------

Co-authored-by: Kevin Kelleher <kevin.kelleher@metabase.com>
Co-authored-by: Nathan Voxland <nathan@metabase.com>

* Don't send empty messages to the LLM

* address review comments from 76755

* UXW-4765 Hide charts (#77488)

* UXW-4765 Hide charts

* Review fixes

* Fix the failing explorations unit tests and Loki stories  (#77564)

* Fix the failing explorations unit tests and Loki stories at the branch tip

* Restore the original all-hidden copy and drop thread headings from it

* Delete the outdated ExplorationGroupVisualization stories and their Loki references

* Rephrase the hide toast to put the chart name first

* UXW-4764 Remove interestingness badges on results (#77544)

* UXW-4764 Remove interestingness badges on results

* Drop e2e assertion for the removed sidebar interestingness markers

* Colocate the interesting and error markers with their only consumers

* Remove the interestingness marker from the timeline picker too

* fix types, lints, formatting

* Uxw 4779 update tab menu to match designs (#77543)

* UXW-4779 Update sidebar tab menu to match designs

* Show a pointer cursor across the whole SegmentedControl

* Give the icon tabs img semantics and hide the decorative icon names

* Let getExplorationSidebarTabsInfo own the tab icon config

* Revert the SegmentedControl root pointer cursor, Mantine already puts it on the labels

* Regenerate modules config after master merge

The merge left 'explorations' and master's new 'entity-retrieval' out of
sorted order, which metabase.core.modules-test asserts on.

* Float the action toolbar pinned to the bottom of the chart canvas (#77667)

* fix types

* revert timelines in documents

* Review fixes: stop requiring the QP's internal cache.impl from other modules

Explorations code that saves and loads query-result snapshots was requiring
metabase.query-processor.middleware.cache.impl directly — an internal
namespace of the query-processor module that other modules aren't supposed
to reach into. Since the module's :api set is generated from actual usage,
those requires had put cache.impl on the module's public surface — something
master never exposed. Modules talk to each other through a small public
facade (metabase.query-processor.core) instead.

The two helpers those callers need — do-with-serialization (write a QP
result as a gzipped blob) and with-reducible-deserialized-results (read one
back) — are now re-exported from the qp.core facade, and the explorations
callers go through it. One consumer can't: queries/cached_result.clj is
itself loaded while qp.core is loading, so requiring the facade from there
would be a circular-load error at boot. It gets a proper home instead — a
new small, documented public namespace,
metabase.query-processor.result-serialization, re-exporting the same two
helpers. With nothing outside the module requiring cache.impl anymore, the
regenerated :api drops it, matching master.

* Review fixes: explorations planner and AI-summary polish

- Nested cardEmbeds: the AI-summary postprocessor wrapped only top-level
  cardEmbed nodes in resizeNode containers, while the card materializer
  walks the whole document — so a cardEmbed the LLM emitted inside e.g. a
  blockquote got a real card but rendered collapsed to zero height. The
  wrapper now walks the tree too (skipping already-wrapped nodes).
- Awareness-tier sort validation: phase 2 validates that embedded
  categorical charts carry an explicit sort instruction, but the validation
  set was built only from top-tier breakouts — an awareness-tier categorical
  chart escaped validation entirely, and its prompt block (slim-block) never
  told the model a sort was required. Both tiers are validated now and
  slim-block emits the same per-chart sort directive as full-block.
- Contextual-scoring gate order: safe-score+describe resolved its scoring
  context (a Card select and a SQL compile) before checking whether the row
  is scorable at all (chart config present, creator known, non-blank thread
  prompt) — wasted DB reads and a query compile for every unscorable row.
  The cheap gates run first now. Its docstring also claimed the LLM gate
  throws and is swallowed; it actually returns nil from a pre-flight cond.
- Discovery-cache TTL: the top-K category discovery cache was a
  process-lifetime LRU, so a stale top-K result could be served forever on
  a long-lived instance; entries now also expire after 15 minutes.
- Chart-name i18n: filtered-subset chart names built their multi-value
  summary as (str (count values) " values") — a hardcoded, unpluralized
  English fragment inside an otherwise-translated name. It uses trun now.
- Docstring/comment corrections: effective-cardinality documented (and
  branched on) a :num-bins bucket that no caller can produce — branch and
  docs removed; run-one-plan-iteration! said it runs "the LLM planner" when
  the default is the mechanical one; a variants-test section comment claimed
  temporal variants add a not-null filter that no variant adds.

Also repoints this module's result-blob serialization calls at the qp.core
facade (the consumer half of the cache.impl cleanup in this stack's first
commit).

* Review fixes: explorations API permissions, documents, and search

- Creating an exploration accepted arbitrary card ids (in blocks) and
  timeline ids without checking the caller could read them, so you could
  attach someone else's private metric or timeline and see its name/events
  later. Both are read-checked at create time now, and timeline hydration
  filters out timelines the viewer can't read. A duplicated timeline id used
  to hit a unique constraint and 500 the request; ids are deduped.
- The restart endpoint re-selected the Exploration row after resetting the
  thread, but the reset never touches that row — the redundant select is
  dropped.
- GET /api/document listed documents that belong to exploration threads,
  which search, recents, and collection items all deliberately hide. It
  applies the same filter now — which as a side effect removes an N+1
  pattern where each listed doc could trigger several permission queries.
- The document validator rejected {:type "doc" :content []} — the exact
  empty document the backend itself creates for new threads. An empty root
  doc is valid now.
- Search: is_published was deliberately hidden from search responses on
  master (#66841) and this branch accidentally re-exposed it — hidden again
  in both search engines. Exploration descriptions are searchable in the
  legacy engine too, matching the new engine.
- Data picker: metrics and dimension groups now use the same
  interestingness filter (a metric could previously reference dimension ids
  that appeared in no group), search matches the displayed
  "<group> - <dimension>" text, and a collection-ids query that ran twice
  per request runs once.
- Consistency: the create event publishes after the transaction commits
  (observers could previously fire before the data was visible).

* Review fixes: metabot / LLM infrastructure

- Asking Claude for extended thinking while also forcing a tool call
  (tool_choice "required") produced a request Anthropic rejects with a 400.
  The guard that already handled this for structured output now covers the
  general tools path too.
- The e2e guard that stops tests from hitting a real LLM provider threw a
  raw MalformedURLException on unparseable URLs; it now fails closed with
  its normal friendly message — and it was moved out of a catch-all that
  would have rerouted it, so it fails loudly.
- The timeline tools handed the LLM broken XML (an attribute quote and a tag
  never closed) and interpolated user-written names/descriptions unescaped,
  letting content break the markup. Output is well-formed and escaped.
- Hardening/docs: the retryable-error check bounds its exception cause-chain
  walk at 10 (a cyclic getCause could loop forever); the
  contextual-interestingness response schema now really forbids extra
  properties (its docstring claimed it already did); docstrings about
  thinking model buckets, return shapes, and inputs corrected.
- Also repairs three test breaks that already existed at HEAD: an arity
  mismatch from the OpenRouter merge, a test asserting thinking blocks are
  dropped (they now stream as reasoning parts), and tests that didn't stub
  the LLM gate's newer usage/permission checks.

* Review fixes: lib-metric, metrics transforms, startup sync, interestingness

- group-by-source was re-exported for JVM Clojure but missing from the
  ClojureScript branch of lib-metric.core; added.
- Metric/measure display-info inherited a default :long-display-name even
  where the method overrode :display-name, so the two could disagree;
  they're set together now.
- A failure in the startup metric-dimension sync aborted server boot
  entirely; it's wrapped to log an error and continue.
- The interestingness "leftovers" backfill loaded every unscored field for
  the database into memory in one select; it now streams one field at a
  time, and the namespace docstring mentions this second recompute trigger.
- Added a unit test covering the dimensions :out transform (kebab-key
  renames + type-value keywordization).

* Review fixes: enterprise perms/sync/deps, cached card query, test quality

- Remote sync: an event payload that simply omitted a key used to satisfy a
  nil condition (e.g. "exploration_thread_id must be nil") — conditions now
  require the key to be present with the right value, checked with
  (find object k), which distinguishes a present-but-nil entry from an
  absent key. Vector values in removal conditions rendered as broken
  equality clauses; they render as IN.
- Dependency graph: smartLink nodes with nil/placeholder ids added nil to
  the dependency set (the sibling cardEmbed branch already guarded this);
  guarded, with regression tests for both branches.
- Test quality: the revisions exclusion test asserted the impl against its
  own private set, so it could never fail; it checks a hardcoded expected
  list now. Exploration bookmarks get real API coverage
  (create/list/delete/ordering/archived), and a mislabeled test string was
  fixed.

* Review fixes: v4 instance-analytics views with explorations usage sources

The AI-usage analytics views translate machine source names ("exploration",
"contextual_interestingness", …) into human-readable labels with a SQL CASE.
The explorations stack added its new sources to the v1 SQL files — but the
views actually installed on instances are v3, and nothing reinstalled them,
so users saw the raw machine names fall through the ELSE branch.

Adds v4 of ai_usage_log and metabot_conversations (v3 plus the new CASE
branches, with one consistent "Explorations" label — v1 said "Exploration"
for the source but "Explorations" for the profile), a migration that
reinstalls the views at v4, an updated pointer comment in usage.clj, and a
parity test that fails whenever a known source/profile is missing a CASE
branch in the latest views — exactly the drift that happened here.

* Review fixes: frontend (minimal) and e2e

Only the low-risk frontend fixes from the review pass, plus the e2e test
fixes; the other frontend findings are deliberately left out to keep this
branch's FE diff small.

- getDimensionIcon existed byte-for-byte in two files; the duplicate module
  is deleted and its one importer now uses the copy in
  common/utils/columns.ts.
- e2e: an assertion that searched for text inside another text node (and so
  always passed) is meaningful now; readSelectedText no longer breaks
  Cypress's retry behavior by asserting after a .then; and the seemingly
  pointless GET /api/metric/:id in seedMetrics got a comment explaining the
  side effect it exists for (it persists metric dimensions).

* Remove the LLM query planner

Explorations plans queries with the mechanical planner only now. Removed:
the LLM planner namespace, its prompt templates (plan.selmer /
repair.selmer) and prompt loader, the prompt-markdown renderers in the
planner-context namespace, the explorations-query-planner setting
(mechanical/llm/auto), and the :skip-no-llm exit for "planner requires an
LLM but none is configured".

pick-planner! now always returns the mechanical planner — kept as a seam
so tests can stub it and a future planner has an obvious dispatch point.
The planner-contract protocol and the shared context builder stay (they
are planner-agnostic), and the query_plan_transcript column stays (every
planner writes a transcript); its remark no longer describes an LLM
exchange.

* remove graph._dimension_value_colors (per bar colors)

* Add row count to explorations API (#77740)

Already available, we just need to persist it and thread it through the
API.

* Format fix

* Exploration UX... um explorations. (#76901)

* click thru

* better comment links

* chart avatars

* avatars

* allow subsubexplorations

* disable explore further for Other group in top n charts and dimension anchored blocks

* fix explore further for binned numeric columns

* comment highlighting, remove avatars

* comment label formatting

* map and table highlighting

* use built in viz click actions popover instead of custom

* fix comment badge in dark mode

* self review cleanup

* fix and simplify new thread toast

* fix be lints, add tests

* fix tests

* add fe, e2e tests

* - POST /:id/explore-further now verifies the clicked page
  belongs to this exploration before copying its block, closing a hole where
  any page in the instance could be copied into any writable exploration.
- key on the stable :explore-filters spec instead of a hash of
  the per-row-reconstructed :dataset_query (which minted fresh :lib/uuids and so
  never hit). build-row-context exposes :explore-filters on the row context for
  the cache to key on.

Also reuse metabase.util/capitalize-first-char in the block title and thread
name helpers instead of hand-rolled str/upper-case.

* Code cleanup

* Gate exploration derived metadata by viewer's permissions

* fix lints

* Code review improvements

* Den/uxw 4741 explorations new sidebar (#77674)

* Update tabs view, add filter button menu

* Fix tests

* Add sidebar items vertical lines + icons

* Other sidebar changes - viewed state, sticky hierarchy

* Show sub-explorations near it's parent item

* Fix issues

* Self review

* Fixes after merge

* Self review

* Review fixes

* Fix empty states

* Review fixes

---------

Co-authored-by: John Swanson <john.swanson@metabase.com>
Co-authored-by: Kevin Kelleher <kevin.kelleher@metabase.com>
Co-authored-by: Nathan Voxland <nathan@metabase.com>
Co-authored-by: Denis Berezin <denis.berezin@metabase.com>

* Explorations self-review cleanup (#77717)

* Consolidate the explorations migration in place

The 063 explorations migration had grown by accretion: tables were created
and then altered by later changesets in the same unreleased file. Since the
branch convention is to edit unmerged changesets in place, the final shapes
are folded into the createTables:

- exploration_query gains params, nullable dataset_query/entity_id, and
  page_id inline (the page_id FK and index stay separate — exploration_page
  is created later in the file, so they can't run earlier).
- stored_result gains data_access_token inline; its backfill changeset was
  deleted outright — created and backfilled in the same release, it could
  only ever run against an empty table.
- exploration_query_result gains chart_stats / metric_description /
  chart_description inline, which also retires the randomly-named changeset
  id v63.gr324asd.
- idx_eqti_query_id deleted: the unique constraint on the same leading
  column already provides that index.

The completed_at and analysis_started_at backfills are deleted outright:
the explorations feature merges to master in one piece, so at upgrade time
no instance can have pre-existing exploration threads to backfill. (They
were also dangerous — they stamped columns the cancel endpoint and the
completion handler gate on being NULL, permanently locking out any thread
in flight during the migration.)

Cosmetics: backfilled default docs are named "Scratchpad" to match runtime
(the backfill said "Findings"); stale remarks corrected (chart_stats is EDN
not JSON, the real query_type variant list, what collection_id actually
defaults to).

Existing dev databases will need a migration reset.

* Set exploration thread started_at in the INSERT

The thread was inserted without started_at and then immediately updated to
set it, on the theory that the background worker must not see the thread
before its dependent document/block/timeline rows exist. The whole create
shares one transaction, so intra-transaction ordering is invisible to the
worker (it reads on another connection; the commit is atomic) — the flag
can simply be set in the INSERT, saving a write.

* Guard exploration restart against in-flight work

The restart endpoint could fire while the planner was still working on the
thread, deleting its rows mid-plan and wedging the thread in a state no
worker would ever pick up again. reset-thread-for-rerun! is now a CAS: it
applies only to a terminal thread (completed_at set) with no query still
mid-execution, and the endpoint returns 409 ("cancel it before restarting")
otherwise. A successful reset leaves the thread exactly in the shape the
planning worker's claim predicate picks up.

The endpoint also published :event/exploration-update, which provably did
nothing: restart changes no Exploration column, and the revision system
skips unchanged objects. The publish is removed; the AI-summary doc reset
still records a Document revision, which is what /mine's last-touched
ordering composes.

* Route mid-stream LLM errors through the provider-friendly rethrow

LLM responses stream lazily; a connection timeout between SSE chunks
escaped as a raw SocketTimeoutException because the provider error handling
only wrapped request setup — the reduction happens later, in the agent
loop. Every provider adapter's SSE reducible (claude, openai, openrouter,
azure, bedrock) is now wrapped so consumption-time failures get the same
translation as request-time errors.

* Grant the read-only timelines scope under metabot-nlq

The explorations metabot profile instructs the model to use the timeline
tools, but those tools were granted only under
:permission/metabot-other-tools while the profile is gated on NLQ — so for
NLQ-only users they were silently filtered out of scope while the prompt
kept referring to them. agent:timelines:* (whose only member is the
read-only list/details scope) is now granted under both permissions.

* Don't retry deterministically-failing fields in the interestingness leftovers pass

The leftovers pass selects on dimension_interestingness IS NULL, so a field
whose scoring deterministically throws was re-attempted on every sync
forever. A per-process attempted-marker now skips fields that already
failed in this process; a restart re-enables retry, so transient failures
still converge. Process-local by design — no schema change, and no sentinel
score leaking into product surfaces that read the column as a number.

* Fail closed when a sandboxed field's user attributes are missing

sandbox-token-for-table returned nil when the field was sandboxed but the
user's sandbox-attribute lookup came up empty — and the downstream
compatibility gate reads nil as "unrestricted". That's fail-open. It now
returns a user-scoped "indeterminate" sentinel that matches nobody else's
token (two indeterminate users don't match each other either): fail-closed.

Also adds direct and end-to-end tests for the impersonation and routing
token producers, which previously had none.

* Require the (card, stored_result) pairing on the cached card-query path

POST /api/card/:id/query with a stored_result_id read-checked the card but
then served whatever snapshot id the client sent — nothing verified the
snapshot actually belonged to that card. The pairing must now exist in
stored_result_use (404 when it doesn't, so an unpaired probe can't confirm
a snapshot exists). Both materialization paths already insert the pairing
rows, and the table ships in the same unreleased release, so no legacy data
lacks them.

The viewer-lens check also denied a nil dataset_query only by accident (a
schema exception swallowed by the catch — and in prod, where mu/defn logs
instead of throwing, it would have failed open); it denies explicitly now.
Plus: consistent t2/select-one forms in the endpoint, a stored_result_use
docstring update, and the generated module config picking up the new
StoredResultUse consumption.

* Apply the personal-collection default when it resolves after first render

The "default new explorations to my personal collection" logic captured
personalCollectionId once in a useState initializer — if the selector
resolved after the first render, the default never applied. It now
reconciles when the id arrives, without overriding an explicit user choice.

* Move the explorations migrations to v64

The v63 release branch has been cut, so the explorations feature will ship
in v64 and its migrations belong in the 064 directory. Both files move
(20260428_explorations.yaml and 20260519_exploration_bookmark.yaml) and
every changeset ID is reprefixed v63. -> v64.

No changeset content changes. Since we're already editing unreleased
changesets in place on this branch, existing dev databases needed a
migration reset anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* v_metabot_conversations v4: restore include-deleted semantics lost in merge

Master's #77542 (conversation branches) and this branch both created
metabot_conversations/v4 in parallel: master's dropped the
deleted_at IS NULL filters so soft-deleted (rewound/regenerated) messages
are counted; ours added CASE branches for the new explorations usage
sources and profile. The 'Merged from master' resolution kept only our
copy, silently losing the deleted-message semantics — which is why
deleted-messages-included-test and last-message-at-test fail: they assert
master's behavior against our view.

This drops the three filter lines from each dialect's v4 file so v4 is the
union of both changes. Both installing changesets (our v63 and master's
v64) are runOnChange: true and checksum the referenced file, so every
instance reinstalls the corrected view on upgrade — no new version or
migration needed.

* Drop the redundant v_metabot_conversations v4 installer

Master's #77542 (064/20260713_metabot_analytics_views_include_deleted.yaml)
already installs metabot_conversations v4 via a runOnChange changeset against
the same SQL files, which now carry both sides' changes: the explorations
CASE branches and the include-deleted semantics. A second installer here was
redundant — and would have collided with master's changeset IDs
(v64.2026-07-13T00:00:00/01) had this file been mechanically renumbered to
v64 like the other explorations migrations.

The ai_usage_log v4 changeset stays: nothing on master installs that view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Move the ai_usage_log v4 migration to 064

Same reasoning as the explorations tables: v63 is cut, this ships in v64.
The changeset ID takes the next free slot for its date
(v64.2026-07-13T00:00:02 — master's include-deleted file owns 00 and 01)
rather than a mechanical v63->v64 rewrite that would have collided.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Show a crossed-eye indicator with a tooltip on hidden pages in the exploration sidebar (#77907)

* Explorations no AI Summary (#77878)

* Extract AI Summary + document-in-exploration out of explorations

Removes the LLM-generated "AI Summary" feature and the ability to display a
document within an exploration. The feature-restoring (inverse) commit is kept
on branch `explorations-ai-summary`; cherry-pick it to bring the functionality
back.

Backend:
- Delete the explorations.ai-summary.* pipeline, composite, document-perms,
  prompt templates, and their tests
- Drop exploration->Document creation, the /thread/:id/documents endpoints,
  the :documents hydrate, ai_summary_document_id, and the AI-summary reset
- Revert the documents-module coupling: content-visibility gate, the
  document.exploration_thread_id FK/column, the view-log skip, and the
  GET /document list filter; delete documents/validate.clj
- Drop the collection-items show_exploration_documents option and the
  exploration-doc exclusions in recents / search / serdes / remote-sync;
  drop the search exploration-thread-id attribute
- Remove the ai_summary_document_id and document.exploration_thread_id
  migration changesets (edited in place)

Frontend:
- Delete the ExplorationDocument component and its tests
- Remove the AI-summary sidebar node, completion toast, and the document
  API / urls / types; keep the shared comments sidesheet and page routing

* Extract static card-embed in documents out of explorations

Removes the static (cached-snapshot) card-embed capability from documents, so it
can land on its own branch (`static-embed`) independently of the AI-Summary
feature. The feature-restoring (inverse) commit lives on `static-embed`.

Keeps the base `stored_result` model, the exploration query-result cache, and the
cached-result permission helpers (`assert-can-view-cached-result!`) that the
exploration `/query/:id` streaming endpoint uses. Also keeps the general
`report_card.document_id` "card in a document" feature (it predates this work).

Removed:
- The cached-result serve path (`cached-dataset`, `apply-sort`,
  `deserialize-cached-result`, `allowed-chart-sorts`, `viewer-can-view-cached-result?`)
  and the `stored_result_id`/`sort` branch of `POST /api/card/:id/query`
- `stored_result_use.card_id` (column + model arm; keeps `exploration_id`)
- The documents serdes static-node filter (reverted to master's live-only rewrite)
  and the prose-mirror static docstrings
- Frontend: the tiptap `cardEmbed` static rendering (the `stored_result_id` + `sort`
  + `chart_href` trio) across the CardEmbed node/menu, document hooks, redux, and
  `card.ts`; deleted the CardEmbedNode static unit spec

* Fix pre-existing cljfmt/oxfmt formatting to unblock CI

These 4 files (a stray blank line each) are unchanged from the base explorations
branch — the format checks were failing on inherited, not extraction, changes.

* more documents cleanup

* Update Loki Snapshots

* change references from entity (which was page or document) to just page

---------

Co-authored-by: Kevin Kelleher <kevin.kelleher@metabase.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>

* Restore fix

* Fix Metabot exploration tools streaming empty results to the client (#77990)

Since 6912a50e85 ([Metabot] Chat internal format improvements, #77301)
only a tool result's :output string makes it onto the tool-output-available
SSE event; a bare map streams as "". The exploration plan tools returned
bare maps, so the FE — which applies plan edits by parsing the streamed
tool result — silently dropped every edit while the LLM (which still saw
the full result) reported success.

Wrap each tool's payload in {:output (json/encode ...)} and add a
regression test that runs real tool results through parts->aisdk-sse-xf
and asserts the payload survives onto the wire.

* Explorations timeline fixes (#77958)

* fix timelines

* disable timelines for row charts

* Explorations sort order (#77980)

* sort empty pages to bottom

* sort subexplorations by the initial exploration's interestingness

* Add e2e tests

* Explorations use persistent queue (#77901)

* explorations: run planning/queries/scoring on the persistent queue

Switch explorations from the hand-rolled polling runner to the mq persistent
queue: three queues (plan, query, timeline-score) with per-queue on-error
handlers and worker-count-bounded concurrency.

The generic mq subsystem changes this builds on live in a separate branch
(mq-queue-improvements) so they can land on master independently. Until that
lands and flows back into `explorations`, this branch depends on those mq
features (:on-error, :max-concurrent-batches) and won't run standalone.

* Remove explorations settings-test

* Code cleanup

* Code cleanup

* Use in-memory queue backend for e2e tests

The persistent Quartz queue backend stores its durable-job state in the app
DB's QRTZ_* tables. e2e restores app-db snapshots between tests (H.restore),
which drops those tables out from under the running scheduler while the
per-process ensured-jobs cache still believes the jobs exist. Post-restore
publishes then target a non-existent job and never deliver, so exploration
planning produces no queries and the detail-page sidebar stays empty.

e2e is a single node, so the in-memory backend (no app-db state, unaffected by
snapshot restore) is the correct fit. Set via the :e2e alias so it applies to
both local (runFromSource) and CI (runFromJar, via getJvmOptsFromDepsEdn).

* fix type

* Remove Timeline interestingness from explorations (#78013)

* Remove Timeline interestingness from explorations

Removes the LLM-driven per-(query, timeline) interestingness scoring
feature and all of its wiring

* Improve names (#77862)

* improve names

* review changes

* fix dimension disambiguation not working

* Fix explore-further on expression columns (#77900)

1. The 500 — normalize-target-ref (in metrics/transforms.clj) silently dropped the expression name. The :field ref schema has a legacy-form decoder that relocates the id into MBQL-5 position; the :expression schema has none, so a
  valid legacy ["expression" "Gear"] normalized to [:expression {…} nil]. That nil-named ref then failed Malli validation inside find-matching-column mid-request → 500.
  2. The drill couldn't work anyway — a top-n-other chart breaks out on a synthetic CASE expression that exists only in the variant query, never on the metric card. So even a valid expression ref could never resolve against the card.

* Code cleanup

---------

Co-authored-by: Nathan Voxland <nathan@metabase.com>

* Update Loki Snapshots

* Improved LLM error handling

* Fixed queueng of explore-further

* Fix e2e tests

* Add copy link hotkey support

* Fix e2e tests

* Fix CI

* Fix after merge

* Make highlighted state in line charts more obvious when hovering over comment badge (#77998)

* show tooltip when highlighting comment for cartesian charts

* set blurScope to blur across stacked chart grids, only show tooltip for single line series

* review feedback

* add types file that was supposed to be included with 77998

* self review

* Auto-switch the add metrics modal to the All tab when the library has nothing to show (#78070)

* Fix back button (#77995)

* Fix back button

Co-Authored-By: Andrei Chernykh <a.d.chernykh@gmail.com>

* Remove the stray /question/explore route, fix the entry spec's removed setMode prop, and clean up dead code

* Keep the research draft and conversation alive across the entry/plan step and browser back/forward via a shared provider on the research route

* Fold the plan-step URL assertion into the existing manual-flow e2e test instead of a separate one

---------

Co-authored-by: Ethan McCue <ethan@mccue.dev>

* Explorations: remove dormant extended-thinking support; unify LLM timeouts (#78173)

* Remove unused Anthropic extended-thinking support

Extended thinking was fully dormant: the Claude adapter supported it
(streaming translation of thinking blocks, config normalization, request-body
support) and call-llm-structured-with-trace forwarded a :thinking opt, but no
production caller ever passed :thinking, and the multi-turn agent path
(call-llm) never accepted it at all. Remove the adapter support and the
structured-path plumbing until a real consumer exists.

This also resolves several open review questions about the thinking code
(signature round-trip on multi-turn continuations, structured-output
guarantees under thinking, per-model capability guards) by removing the code
they applied to.

* Unify LLM HTTP timeout knobs across metabot.self and llm.anthropic

metabase.metabot.self.core/request hardcoded its connection (10s) and
socket (120s) timeouts, diverging from the metabase.llm.anthropic HTTP
path, which already reads the operator-tunable llm-connection-timeout-ms /
llm-request-timeout-ms settings. Operators had two uncoordinated knobs.

Delete the two private constants and read the settings at call time in
`request` (keeping the per-request `(merge req)` override). Raise the
setting defaults to the former constants' values (5000 -> 10000,
60000 -> 120000) so no path loses its safety margin, and move the
constants' streaming/inter-byte rationale into the defsetting docstrings.

Both LLM HTTP paths now share a single operator knob pair.

Resolves PR #1 review comment 2.

* UXW-4758 Ability to view all timeline events

* Show proper empty state message in sidebar for initial load

* Bump prefer-with-dynamic-fn-redefs ratchet to 28

The explorations MQ queue tests need three real with-redefs (not
mt/with-dynamic-fn-redefs): the handlers run on an MQ worker thread that
does not carry the calling thread's dynamic bindings, as documented
inline at test/metabase/explorations/queues_test.clj:102,153,265. Seed
the budget to match so metabase.core.kondo-ratchet-test passes.

* fix regression of #77667 caused by #76901

* Restore explorations CASE branches in v_metabot_conversations (v6)

master's #77549 rewrote v_metabot_conversations to v5 (summary->title
rename) without the explorations source/profile CASE branches that v4
carried, so the exploration/contextual_interestingness sources and the
explorations profile surfaced as raw machine names and failed
metabase-enterprise.metabot.usage-test/known-sources-and-profiles-have-view-case-branches-test
across every dialect.

v5's installer is not runOnChange and is already released on master, so
edit it in place would break the checksum. Instead add v6 (v5 + the three
branches) and a runOnChange installer that reinstalls the view after the
v5 rename, matching the v4 installer pattern.

* fix errors caused by merging master

* UXW-4758 Close timeline events sidebar when comments are opened

* Explorations queries api performance (#78175)

* revert error message display changes

* Avoid a full fingerprint version increment

* Fix unit tests error logs

* Removed the composed LRU + TTL cache that is not supported by the core.cache factories. The data should be naturally bounded enough that TTL should be good enough.

* Added scopes for explorations in metabot

* Explorations PUT safety (#78188)

Don't allow random assignment on PUT /api/exploration/:id

* Pick the right column as the metric for interestingness scoring (#78269)

Pick the right column as the metric

* Explorations unstranded threads (#78250)

Fixing ways threads can be stranded

* Hide SQL error messages from API response

* Explorations pass research plan (#78236)

Correctly pass research plan

* Revert in-place edits to historical instance-analytics view SQL

The explorations CASE branches were added directly to the v1/v4 view SQL
files, but those files back runOnChange changesets: editing them changes
the checksum, so on an already-migrated DB the old installers re-fire.
metabot_conversations v1 still selects c.summary, which the v64 rename
(summary -> title) has dropped, so booting the branch on a master-migrated
DB fails with 'column c.summary does not exist' (all six cross-branch
migration CI jobs).

The branches live on in the branch-new final versions (ai_usage_log v4,
metabot_conversations v6), so nothing is lost on fresh installs.

* Raise prefer-with-dynamic-fn-redefs ratchet to 29

The branch added four with-redefs ignores in explorations/queues_test.clj
(all justified: the redefined handlers run on MQ worker threads, which do
not see thread-local dynamic bindings, so mt/with-dynamic-fn-redefs cannot
work there) but the budget was only bumped to 28. Verified all 29 ignores
in the tree still sit on live with-redefs calls, so none can be removed
instead.

* Improving exploration error handling

* Fix with-current-user binding in exploration runner (#78163)

* Better error vs. empty handling in query cache

 Conflicts:
	src/metabase/explorations/query_plan/variants.clj

* self review

* Added FKs

* fix e2e

* Added visible-metric-cards-where-clause to queries.core

* Cleaned up removed-in-master agent:table scope

* Reuse Event card design from QueryBuilder

* Handle prompt injection

* Fix hotkeys hijack default actions

* Reuse the query instead of continually re-running it

* Fix extra rerenders on queries polling

* Review fixes

* Review fixes

* Fix chart skeleton for all screens

* review comments

* Fix Andrey's review comments

* Fix Andrey's review comments

* Fix Nemanja's review comments for e2e

* Fix Nemanja's review comments for e2e

* more review comments

* Ensure user has read permissions on cards when doing an explore-further

* Handle numeric PKs/FKs better in explorations (UXW-4757) (#77991)

Numeric PK/FK dimensions produced unusable one-bar-per-id charts and were
auto-selected as interesting dimensions. Two root causes:

1. The query planner treated any numeric dim as auto-binnable, but...
   they're not, and we emitted unbounded bare breakouts, and ruled out
   the top-n-other variant.

2. Dimension interestingness hard-zeros PKs but not FKs, and the
   cardinality scorer's auto-binnable branch gave numeric FKs 0.9 for a
   binned chart the QP can never produce, so they got auto-selected.

Fixes:

- default-bucket-for-dim returns nil for :Relation/* dims, so numeric keys
  band by raw distinct count: <=100 -> default, >20/unknown -> top-n-other.
- top-n-other stringifies its CASE THEN values so numeric dims compile to
  a single-typed CASE (mixed numeric/string CASE breaks strict warehouses).
- type-penalty hard-zeros numeric FKs (semantic FK + numeric effective/base
  type). Non-numeric FKs (e.g. a country-code FK) stay scoreable.
- The cardinality scorer excludes :Relation/* from the auto-binnable
  branch, matching lib's own binning rule.

* UXW-4475: self-heal missing metric dimensions in explorations read path (#78061)

Metrics whose dimensions were never successfully synced (dimensions NULL) -
e.g. metrics created on a model before it had result_metadata, or SQL-model
metrics predating the source-card sync fix - showed zero dimensions in the
explorations picker and Metabot research tools forever, because those paths
read persisted dimensions without ever syncing.

hydrated-metrics now runs sync-dimensions! for loaded metrics that have a
dataset_query but empty dimensions, then reloads the healed rows. Metrics
that compute nothing persist nothing and retry cheaply on a later request
(app-DB reads only, no warehouse queries, no write when nothing changed).

* Fix stored-snapshot permission gate ignoring card-sourced queries (#78455)

The data-access-lens capture derived its table set from
query->source-table-ids on the raw stored dataset_query, which only
collects numeric :source-table ids. For queries built on cards
(:source-table "card__N"), neither the creator's nor the viewer's token
picked up any tables, so both sandbox dimensions were empty and
data-access-compatible? returned true — letting a sandboxed viewer
stream an unsandboxed creator's snapshot.

Add query-perms/query->resolved-source-table-ids, which preprocesses the
query (as admin, via the existing preprocess-query helper) so source
cards — nested chains and card-sourced joins included — resolve to their
underlying table ids, and use it at both lens-capture sites (the
exploration runner's token write and the cached-result read gate). The
preprocess binds qp.i/*skip-middleware-because-app-db-access*, the same
flag the offline semantic checker uses, so the routing/impersonation
preprocess middlewares don't resolve the *viewer's* destination/role
(and throw on lapsed premium features) while we're only after the
query's table footprint. The helper throws when the card chain can't be
resolved (deleted card); non-admin viewers are then denied while
admins — never sandboxed, impersonated, or routed off the router db —
fall back to the same admin-only access as a nil token.

Also make stored_result.dataset_query NOT NULL via a follow-up
addNotNullConstraint changeset: the gate derives the viewer's lens from
it, so a query-less snapshot has no defensible verdict. A gate caller
passing a map without the key now gets a loud exception (500) instead of
a quiet fallback, so trimmed-row bugs surface in development.

Also fix explore-further-nested-thread-name-omits-metric-prefix-test to
reference a real exploration_page instead of a hardcoded id 999 that
violates the source_page_id FK on fresh databases.

* Fix build

* Fixed comment

* Better cached result testing

 Conflicts:
	src/metabase/queries/cached_result.clj

* Hash data access token info

* Added test from before

* Fix unit tests

* Fix Andrey's review comments

* Sloan review comments

* Explorations - fix metrics case (#78411)

* Add delay for Sidebar shimmer text, split ExplorationTreeNode

* Fixes for routing after merge

* Latest reviews fixes

* UXW-4908 new Comments UI

* add more analytics

* Code review fixes

* Fix scroll to view for focused comment

* Correct permissions "lenses" (#78697)

* Correct permissions "lenses"

Some early code had a huge, obvious bug, asserted in tests, for database routing.

For explorations, because queries are pre-computed, we need to calculate "lenses" representing the state
of database routing, sandboxing, and impersonation. These lenses are calculated when the exploration is
created, and then calculated when someone attempts to view the exploration. The idea is that if the lens
reveals that the viewer would have been able to view the underlying queries, then they should also be able
to view your exploration.

Until now, a viewer who was "unrestricted" on one of those dimensions was allowed to see a restricted
snapshot. I.e., a "non-sandboxed" user could see a sandboxed user's exploration, a "non-impersonated" user
could see an impersonated user's snapshot, and a "routed-to-the-router" user could see a "routed-to-a-destination"
user's snapshot.

The issue here... "unrestricted" does not necessarily mean "unrestricted." It's entirely possible that:

- User A is sandboxed with a sandbox that replaces Table T with card C, which actually queries Table FT.
  User B is permitted to see Table T (unrestricted) but is blocked on Table FT.

- User A is impersonated - to a *more* permissive role. User B is unimpersonated, but should not be able to see
  the results of a query made by User A.

- User A has database routing, configured to point to `MySecretTenantData`. User B is routed to `__METABASE_ROUTER__`,
  which holds a template schema and maybe some data to play around with. User B should not be able to see the results of
  A's exploration.

This simplifies the code here:

- superusers can see every exploration, regardless of lens

- everyone else sees an exploration's results *ONLY* when their data-access lens is *identical* to the creator's. Same sandbox
  (and relevant user attributes), same impersonation role, same routing destination. Unrestricted only matches unrestricted.

Also hardened the write side: capturing the creator's lens fingerprint used to be best-effort, silently saving a snapshot with
no fingerprint when it failed. Now the exploration query fails visibly instead, and the read side treats a
fingerprint-less snapshot as a bug (denied to non-admins, logged as an error) rather than something to adjudicate.

* Apply suggestions from code review

clean up test descriptions and other docstrings

Co-authored-by: Timothy S. Dean <timothy@timothydean.dev>

* Respond to CR

* fix impersonation test

---------

Co-authored-by: Timothy S. Dean <timothy@timothydean.dev>

* Fixed compile issue

* Address query-plan review comments on #73244

- context.clj: correct the column-fingerprint-for-target docstring (missing
  fingerprints make default/time-facet eligibility fail closed and route
  dims to top-n-other, not the reverse), fix the applicability return-shape
  doc, and extend the ns docstring to cover build-row-context.
- context.clj + variants.clj: remove the dead temporal_dimension_id override
  plumbing (nothing ever sets that param; the mechanical planner never emits
  per-value-time-series items).
- variants.clj: drop the unreferenced known-variants var.
- query_plan.clj + planner.clj: validate every planner result against
  result-schema in the orchestrator, treating a mismatch as a planner
  failure, and document that the schemas are enforced.
- planner.clj: fix the plan! docstring's claim that the ctx shape is
  documented on generate-query-plan!.

* Remove unused planner result schemas instead of enforcing them

Drops plan-item-schema and result-schema from the planner contract (review
follow-up on #73244): they were never applied to any value, and rather than
wiring validation into the orchestrator we document the plan!/result shapes
in prose on the protocol. Also rewords the context ns docstring's
plan/validate/materialize mention accordingly.

* Trim redundant clause from query-plan.context ns docstring

* Fix FE typecheck and unit tests

* Fix some tests

* Fix dimensions display in NewExplorationData

* Select all dimensions for a metric by default

* Fix metric-dimensions dependency test vs metric dimension auto-sync

The test (from master) created a metric card with hand-written
dimension_mappings and nil dimensions. On this branch a metric with nil
dimensions counts as uncurated, so the after-insert dimension auto-sync
seeds dimensions from the query and overwrites the provided mappings —
the mapped categories table never became a dependency.

Set the mappings via a plain update after insert instead: the
after-update hook only re-syncs when dataset_query changes, so they
stick, and the test still exercises what it means to — the dependency
backfill turning mapped-dimension tables into table dependencies.

* Fix metric dashcard perms test vs metric dimension auto-sync

The tests (from master) expect a metric dashcard query to return the
bare aggregation. On this branch the after-insert auto-sync seeds
metric dimensions, and qp.dashboard applies the default time dimension
as a breakout, turning the scalar count into a monthly series.

Suppress the auto-sync when creating the metric so the test stays
focused on what it exercises: permissions on the metric's source
model.

* Fix FE typecheck and unit tests

* Skip tests that will be addressed in the next PR

* Fix loki tests

* Add logs to identify e2e test failure reason

* Fix CI e2e failure

* Review improvements

* Redact raw driver errors from the exploration payload

`GET /api/exploration/query/:id` deliberately allowlists its 409 payload and
substitutes a generic message, because a driver error names tables, columns,
and row values from a query the reader may not have permission to run.

`GET /api/exploration/:id` hydrates the same `ExplorationQuery` rows onto its
response and returned `:error_message` verbatim, so the raw error reached
anyone who could read the exploration. Declaring the key in the response
schema does not help: a `defendpoint` response schema validates and encodes
but does not strip.

Give the substitution one home in `redact-query-error` and route both paths
through it — `hydrate-exploration` covers all five `::HydratedExploration`
endpoints at a single point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Track data_access_token with thread and query instead of relying on the (not always there) cached result

* Key the derived-data verdict on both query projections

* Remove eager dimension-sync for metrics

* Fix unit tests after merge

* Fix typecheck

* Code review fixes

* Revert "Select all dimensions for a metric by default"

This reverts commit d5e8883c95.

* Code review fixes

* Fixing tests

* Fix e2e test

* fix issues after merge

* fix error when saving exploration in Our Analytics

---------

Co-authored-by: Nathan Voxland <nathan@metabase.com>
Co-authored-by: Denis Berezin <denis.berezin@metabase.com>
Co-authored-by: John Swanson <john.swanson@metabase.com>
Co-authored-by: Andrei Chernykh <andrei.chernykh@metabase.com>
Co-authored-by: Kyle Doherty <5248953+kdoh@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Metabase Automation <github-automation@metabase.com>
Co-authored-by: Ethan McCue <ethan@mccue.dev>
Co-authored-by: Timothy S. Dean <timothy@timothydean.dev>
2026-08-05 21:34:13 +00:00
Oleh K
95e367f533 Remove color-mix from theme color definitions (#78665)
* GDGT-2851 Remove color-mix from theme color definitions

color-mix can only be resolved by a browser, so any theme color using it
breaks static viz, which renders on the server (see UXW-4690, where the
gauge failed on background_page-tertiary-inverse).

Per design's proposal, introduce a new orion-95 stop and use solid scale
values for the page backgrounds:

- dark: page primary/secondary/tertiary -> orion 95/100/110
- light: page primary/secondary/tertiary inverse -> orion 95/100/110

The remaining mixes are collapsed to their literal equivalents:
shadow-default to a plain rgba, and tooltip-background-focused to the
next stop down from tooltip-background.

The mixes that derive from whitelabel colors (core-brand, core-filter,
core-summarize) are left alone, since their values aren't known ahead of
time. A unit test now guards against reintroducing color-mix anywhere
else in the theme definitions.

* Update Loki Snapshots

* Remove var() references and non-hsla literals from theme definitions

Static viz feeds theme colors straight into Color(), which only parses
literals, so a var() throws there exactly like a color-mix does — even
when it points at a perfectly ordinary color. Collapse the remaining
var() references to the underlying base color, and normalize the few
hex and bare-keyword values to hsla so every theme color is a literal.

The guard test drops the transitive resolver it no longer needs and just
asserts that every non-whitelabel color is an hsla literal.

* Fix regression in pivot table

* Empty commit to trigger CI

* Manually update pivot table loki snapshot

* Update e2e colour assertions for the new background tokens

Dark background_page-primary moved from orion-100 to the new orion-95,
so anything asserting rgb(7, 23, 34) for it now expects rgb(12, 28, 39).

The dark mode body assertion was matching color(srgb 0.0204 0.06792
0.0996) — Chrome's computed form for the old color-mix on
background_page-secondary. That token is a plain hsla now, so Chrome
reports it as rgb(7, 23, 34).

* Fix e2e

* Fix e2e

* Revert changes in assertDarkMode

* Fix oss e2e spec

* Revert SDK theming assertions to the deprecated background token

The SDK theme presets resolve colours through the first entry of
SDK_TO_MAIN_APP_COLORS_MAPPING, and for `background` that is the
deprecated `background-primary`, which still points at orion-100.
`background_page-primary` sits at index 1 and never gets read, so
table-root is unaffected by the move to orion-95.

* Address review feedback on the colour theme changes

Move the gold and upsell-gem literals into base-colors, since each was
repeated across both theme files. Drop the intermediate staticColors
binding in the guard test in favour of a second filter, and drop the
`?? {}` on theme.colors, which is a required field.

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-07-29 13:06:47 +02:00
Egor Grushin
a9731f6e65 style(sdk-bundle): Alert component redesign (#78260)
* redesign Alert, remove deprecated Alert component

* fix styling so far

* update alert stories

* additional changes

* small fix

* refactor Alert config

* address PR's comments

* chores

* Update Loki Snapshots

* addressing the designing feedback in places

* addressing the designing feedback in the Alert itself

* add loki tests

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-07-24 08:08:28 +02:00
Nick Fitzpatrick
528ae0a381 Update table loading skeleton to use patterns (#78361) 2026-07-23 09:43:59 -03:00
Nick Fitzpatrick
cd9d166077 Fix trend viz in FE PDFs (#77612) 2026-07-17 11:08:38 -03:00
Ethan McCue
8705016252 Fix gauge breaking in static-viz (#77724)
Fix gauge color crashing

Co-authored-by: Kevin Kelleher <16599994+kevinkelleher12@users.noreply.github.com>
2026-07-16 13:46:56 +00:00
Kevin Kelleher
03a8afdba8 Truncate categorical axis labels rather than not showing them (#77131)
* truncate categorical axis labels

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-07-15 07:30:44 -04:00
Egor Grushin
7b3ed42a61 GDGT-2585 Replace deprecated Input component (#77559)
* initial migration

* fix link input size

* remove dead files

* Use Input.ClearButton for textinput clear icon

Replace hand-rolled Tooltip+ActionIcon+Icon clear pattern w/ Mantine
Input.ClearButton across 5 sites; c=text-secondary + rightSectionPointerEvents=all.
Comment why LinkViz needs pointer-events on both root+input.

* fix unit test

* Update loki refs for parameter search Input->TextInput

* Use Input.ClearButton in PermissionsEditorContent; rm blank line in SearchBar styled

* addressing the PR's comments
2026-07-14 22:49:55 +01:00
Kamil Mielnik
a9e1b0edf7 GDGT-2454 Badge (#74714) 2026-07-09 21:47:43 +07:00
Phoomparin Mano
07c824b6fb fix(sdk): derive dark mode color scheme based on sdk theme colors (#77269)
* add css variables for styled components

* commit loki snapshot change

* add resolved theme mocks

* use real theme color values
2026-07-08 16:24:19 +00:00
Oleh K
3512caf5ee refactor: Migrate feedback, components, misc, upsell, illustration & admin color tokens (#76871)
* Migrate remaining color tokens to new names (GDGT-2535)

Migrate the remaining legacy design-system color tokens to their new
names per the GDGT-2517 migration guide:

- feedback: error/danger -> feedback-negative, success ->
  feedback-positive, warning/gold -> feedback-warning
- misc/core: info -> core-info, white -> core-white, shadow ->
  shadow-default, overlay -> modal-overlay, focus -> input-focus,
  metabase-brand -> core-metabase_brand
- admin: admin-navbar* -> navbar-admin*
- upsell/illustration: per guide
- remove unused bronze/copper/silver

Covers all reference forms: CSS `var(--mb-color-*)`, `color()`/helper
calls, Mantine `c`/`bg`/`color`/`iconColor` props (string and ternary),
object-literal `color:`/`iconColor:` keys, `ColorName` return values,
arrays, and `Record<_, ColorName>` value maps. Tightens `Undo.toastColor`
from `string` to `ColorName` so future token removals fail at compile
time. Maps the `Alert` component's semantic `color` prop to feedback
tokens internally so its call sites stay unchanged.

Also sweeps up brand/filter/summarize consumer stragglers left over from
the earlier (merged) migration. SDK/whitelabel input keys, the
derive-theme alias definitions, and static-viz's local palette are left
on their existing names by design.

* Migrate missed success-secondary props in StepperWithCards

The .module.css was migrated to feedback-positive-selected but the two
inline c="success-secondary" props (Done check icon + label) were missed.

* Update Loki Snapshots

* Fix toastColor type widening in metabot toast-prop helpers

Undo.toastColor tightened to ColorName in this branch; the two get*ToastProps
helpers returned object literals where toastColor widened to string (missing
as const, unlike their iconColor/dismissIconColor siblings).

* Update TransformListPage sync-color spec to migrated feedback token names

utils.ts color map was migrated (success/warning/danger -> feedback-*) but
the spec assertions still expected the old names.

* Update SDK theme + treemap specs for feedback/misc token mirrors

- get-embedding-theme: SDK mapping now fans text-white->core-white and
  focus->input-focus, so the expected override gains those keys.
- treemap option: getColor("white") became getColor("core-white"); add
  core-white to the test's MOCK_COLORS palette.

* Update Loki Snapshots

* Update toast color-attr assertions to migrated feedback token names

* Cleanup

* Migrate remaining color tokens in Button and Table stories

* Add explainer comment for colors naming convention

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-07-07 15:34:02 +02:00
Nick Fitzpatrick
6bf214f2c2 update Echarts to 6.1.0 (#75139)
* echarts 6.1.0

* adjusting BE test
2026-07-06 23:44:56 +05:30
Jakub Chodorowicz
3487a7801f GDGT-2750: Treemap renders incorrectly in PDF export (#76996)
* fix treemap wrong size in grid driven pdfs

* add missing loki screenshot
2026-07-06 15:19:43 +02:00
Oleh K
132a7e674a refactor: Migrate icon tokens (disabled/secondary swap) (#76792)
* GDGT-2534 Migrate icon tokens (disabled/secondary swap + text aliases)

Per design, the icon tokens are corrected: `icon-disabled` and
`icon-secondary` swap meaning, and all four icon tokens become runtime
aliases of their text counterparts.

- Swap consumers: `icon-disabled` <-> `icon-secondary` call sites.
- Redefine in both themes:
  - icon-primary  -> var(--mb-color-text-primary)   (value unchanged)
  - icon-secondary -> var(--mb-color-text-secondary)
  - icon-disabled  -> var(--mb-color-text-disabled)
  - icon-brand    -> var(--mb-color-text-brand)      (brand[40] -> brand[50])

Token names are unchanged, so there's nothing to remove at cleanup. Icon
tokens aren't SDK public theme keys and aren't used in static-viz, so the
var() aliases are safe everywhere.

* Update Loki Snapshots

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-07-02 13:00:13 +02:00
Oleh K
8f5eb11fba refactor: Migrate text and syntax tokens (#76508)
* GDGT-2533 Migrate text and syntax tokens

Sweep all consumer references from the legacy text/syntax tokens to their
new names (added in the foundation PR):

- text-tertiary -> text-disabled (and -inverse) — value shifts from solid
  orion to orion-alpha (intended recolor)
- text-hover -> text-brand-hover (value unchanged)
- syntax-parameters(-active) -> text-syntax-parameter(-active)
- saturated-{blue,green,yellow,red,purple} -> core-{blue,green,yellow,red,
  purple}-saturated (value unchanged)

`text-tertiary` and `text-hover` are also SDK public theme keys, so the
SDK_TO_MAIN_APP_COLORS_MAPPING right-hand sides gain the new internal names
as second targets and the public keys are left unchanged.

Per design review, the five saturated colors are renamed to
`core-{color}-saturated` palette primitives rather than `text-syntax-{role}`;
this PR adds those token definitions, removes the superseded text-syntax-*
ones, and keeps the legacy saturated-* definitions emitting until cleanup.
`text-secondary-opaque` is left untouched (open design question).

* Formatting

* Update Loki Snapshots

* Keep public SDK theme key text-tertiary in embedding examples/tests

text-tertiary is an embedder-facing SDK theme key (MetabaseColors), not an
internal token. The blanket sweep wrongly renamed it to the internal
text-disabled in three embedding spots that live outside the type-check
scope (e2e specs + the CLI scaffold snippet). Revert those to the public
text-tertiary; the SDK maps it internally via SDK_TO_MAIN_APP_COLORS_MAPPING.

---------

Co-authored-by: Metabase Automation <github-automation@metabase.com>
2026-06-29 16:36:18 +02:00