@morscherlab/mint-sdk 1.3.0-beta.6 → 1.3.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{ResourcePickerListInternal-BEXnyBNz.js → ResourcePickerListInternal-CFAw4vTZ.js} +20 -17
  2. package/dist/ResourcePickerListInternal-CFAw4vTZ.js.map +1 -0
  3. package/dist/components/SettingsModal.schema.d.ts +1 -1
  4. package/dist/components/SettingsModal.vue.d.ts +2 -0
  5. package/dist/components/index.js +3 -3
  6. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +9 -3
  7. package/dist/{components-C-CAD2Ma.js → components-GDDvof8D.js} +18 -14
  8. package/dist/components-GDDvof8D.js.map +1 -0
  9. package/dist/composables/index.js +3 -3
  10. package/dist/{composables-BKhsoONv.js → composables-D6uqp0UF.js} +3 -3
  11. package/dist/{composables-BKhsoONv.js.map → composables-D6uqp0UF.js.map} +1 -1
  12. package/dist/index.js +5 -5
  13. package/dist/install.js +1 -1
  14. package/dist/styles.css +93 -61
  15. package/dist/templates/index.js +1 -1
  16. package/dist/{templates-DcxyATPT.js → templates-BU1pet28.js} +2 -2
  17. package/dist/{templates-DcxyATPT.js.map → templates-BU1pet28.js.map} +1 -1
  18. package/dist/types/components.d.ts +4 -0
  19. package/dist/{validation-kbTxfKYD.js → validation-Dmp9Q5W0.js} +2 -2
  20. package/dist/{validation-kbTxfKYD.js.map → validation-Dmp9Q5W0.js.map} +1 -1
  21. package/package.json +1 -1
  22. package/src/__tests__/components/ExperimentSelectorModal.test.ts +12 -5
  23. package/src/__tests__/components/ResourcePickerListInternal.test.ts +23 -4
  24. package/src/__tests__/components/SettingsModal.test.ts +22 -0
  25. package/src/__tests__/styles/controlRefreshRules.test.ts +22 -3
  26. package/src/__tests__/styles/menusShellRefresh.test.ts +14 -5
  27. package/src/components/AppTopBar.vue +1 -0
  28. package/src/components/ExperimentSelectorModal.vue +4 -1
  29. package/src/components/SettingsModal.schema.ts +4 -2
  30. package/src/components/SettingsModal.vue +4 -2
  31. package/src/components/internal/ResourcePickerListInternal.vue +21 -27
  32. package/src/styles/components/app-page-selector.css +3 -3
  33. package/src/styles/components/app-pill-nav.css +1 -1
  34. package/src/styles/components/app-top-bar.css +16 -12
  35. package/src/styles/components/experiment-selector-modal.css +3 -9
  36. package/src/styles/components/resource-picker-list.css +70 -36
  37. package/src/types/components.ts +4 -0
  38. package/dist/ResourcePickerListInternal-BEXnyBNz.js.map +0 -1
  39. package/dist/components-C-CAD2Ma.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.3.0-beta.6",
3
+ "version": "1.3.0-beta.8",
4
4
  "description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -129,25 +129,32 @@ describe('ExperimentSelectorModal', () => {
129
129
  expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
130
130
  })
131
131
 
132
- it('should lead the row title with the experiment code in mono', () => {
132
+ it('should show the experiment code as a pill on the second line', () => {
133
133
  experiments.value = [experiment()]
134
134
  const wrapper = mountModal({ modelValue: true })
135
135
 
136
136
  expect(wrapper.get('.mint-resource-picker__code').text()).toBe('EXP-007')
137
137
  })
138
138
 
139
- it('should state the status as text on the second row line', () => {
139
+ it('should state the status as text in the right-hand column', () => {
140
140
  experiments.value = [experiment()]
141
141
  const wrapper = mountModal({ modelValue: true })
142
142
 
143
- expect(wrapper.get('.mint-resource-picker__meta').text()).toContain('Ongoing')
143
+ expect(wrapper.get('.mint-resource-picker__aside').text()).toContain('Ongoing')
144
144
  })
145
145
 
146
- it('should check the current experiment row', () => {
146
+ it('should tint the status with the experiment status variant', () => {
147
+ experiments.value = [experiment()]
148
+ const wrapper = mountModal({ modelValue: true })
149
+
150
+ expect(wrapper.get('.mint-resource-picker__status').classes()).toContain('mint-resource-picker__status--primary')
151
+ })
152
+
153
+ it('should fill the current experiment row', () => {
147
154
  experiments.value = [experiment()]
148
155
  const wrapper = mountModal({ modelValue: true, currentExperimentId: 7 })
149
156
 
150
- expect(wrapper.find('.mint-resource-picker__check').exists()).toBe(true)
157
+ expect(wrapper.find('.mint-resource-picker__row--selected').exists()).toBe(true)
151
158
  })
152
159
 
153
160
  it('clears the shared store from the clear-selection footer', async () => {
@@ -40,8 +40,27 @@ describe('ResourcePickerListInternal', () => {
40
40
  expect(setup().get('.mint-resource-picker__code').text()).toBe('EXP-042')
41
41
  })
42
42
 
43
- it('should join the meta and date on the second line', () => {
44
- expect(setup().get('.mint-resource-picker__meta').text()).toBe('Zhang · 09-24')
43
+ it('should render the code as the shared gray code pill', () => {
44
+ expect(setup().get('.mint-resource-picker__code').classes()).toContain('mint-exp-code')
45
+ })
46
+
47
+ it('should tint the status by its variant', () => {
48
+ const wrapper = setup({
49
+ describe: (item: unknown) => ({ title: (item as Item).name, status: { label: 'Completed', variant: 'success' } }),
50
+ })
51
+ expect(wrapper.get('.mint-resource-picker__status').classes()).toContain('mint-resource-picker__status--success')
52
+ })
53
+
54
+ it('should keep the title line to the title alone', () => {
55
+ expect(setup().get('.mint-resource-picker__title').text()).toBe('Liver microsome stability')
56
+ })
57
+
58
+ it('should lead the second line with the code and meta', () => {
59
+ expect(setup().get('.mint-resource-picker__meta').text()).toBe('EXP-042Zhang')
60
+ })
61
+
62
+ it('should put the date in the right-hand column', () => {
63
+ expect(setup().get('.mint-resource-picker__aside .mint-resource-picker__date').text()).toBe('09-24')
45
64
  })
46
65
 
47
66
  it('should mark the selected row', () => {
@@ -49,8 +68,8 @@ describe('ResourcePickerListInternal', () => {
49
68
  expect(wrapper.findAll('[role="option"]')[1]!.attributes('aria-selected')).toBe('true')
50
69
  })
51
70
 
52
- it('should draw a trailing check on the selected row only', () => {
53
- expect(setup({ selectedKey: 2 }).findAll('.mint-resource-picker__check')).toHaveLength(1)
71
+ it('should fill the selected row only', () => {
72
+ expect(setup({ selectedKey: 2 }).findAll('.mint-resource-picker__row--selected')).toHaveLength(1)
54
73
  })
55
74
 
56
75
  it('should emit select when a row is clicked', async () => {
@@ -540,6 +540,28 @@ describe('SettingsModal', () => {
540
540
  expect(wrapper.findAll('.mint-modal__rail-section').map(heading => heading.text())).toEqual(['Plugin', 'Personal'])
541
541
  })
542
542
 
543
+ it('should list and open Appearance first when appearanceFirst is set', () => {
544
+ const wrapper = mountSettingsModal({
545
+ tabs: [{ id: 'plugins', label: 'Plugins' }],
546
+ showAppearance: true,
547
+ appearanceFirst: true,
548
+ layout: 'vertical',
549
+ })
550
+
551
+ expect(wrapper.get('.mint-modal__rail-item--active').text()).toContain('Appearance')
552
+ })
553
+
554
+ it('should label a manual tab rail section from its section', () => {
555
+ const wrapper = mountSettingsModal({
556
+ tabs: [{ id: 'plugins', label: 'Plugins', section: 'Platform' }],
557
+ showAppearance: true,
558
+ appearanceFirst: true,
559
+ layout: 'vertical',
560
+ })
561
+
562
+ expect(wrapper.findAll('.mint-modal__rail-section').map(heading => heading.text())).toEqual(['Personal', 'Platform'])
563
+ })
564
+
543
565
  it('should not render rail section headings when appearance is hidden', () => {
544
566
  const wrapper = mountSettingsModal({ schema: railSchema })
545
567
 
@@ -184,11 +184,30 @@ describe('SDK control refresh style rules', () => {
184
184
  expect(rule).toMatch(/flex-wrap:\s*wrap/)
185
185
  })
186
186
 
187
- it('should pad resource picker rows so two text lines fit the 44px row when scanning resource-picker-list.css (8b)', () => {
188
- // 16.25px title + 1px gap + 15px meta + 10px padding + 1px border = 43.25px, under min-height.
187
+ it('should pad resource picker rows to the roomier two-column layout when scanning resource-picker-list.css', () => {
189
188
  const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
190
189
  const rule = /\.mint-resource-picker__row\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
191
- expect(rule).toMatch(/padding:\s*5px 12px/)
190
+ expect(rule).toMatch(/padding:\s*10px 12px/)
191
+ })
192
+
193
+ // Group bars and the list frame were inset by the modal body padding; the list now runs edge to edge.
194
+ it('should drop the modal body padding around the resource picker when scanning resource-picker-list.css', () => {
195
+ const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
196
+ const rule = /\.mint-modal__body:has\(> \.mint-resource-picker\)\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
197
+ expect(rule).toMatch(/padding:\s*0/)
198
+ })
199
+
200
+ it('should pad resource picker group bars to the modal gutter when scanning resource-picker-list.css', () => {
201
+ const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
202
+ const rule = /\.mint-resource-picker__group\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
203
+ expect(rule).toMatch(/padding:\s*0 1\.25rem/)
204
+ })
205
+
206
+ // The list sat in a bordered card inside the modal card.
207
+ it('should draw no frame around the resource picker list when scanning resource-picker-list.css', () => {
208
+ const css = stripComments(readFileSync(component('resource-picker-list.css'), 'utf-8'))
209
+ const rule = /\.mint-resource-picker__frame\s*\{([^}]*)\}/.exec(css)?.[1] ?? ''
210
+ expect(rule).not.toMatch(/(^|;)\s*border:/)
192
211
  })
193
212
 
194
213
  it('should paint the clear-selection link with primary ink when scanning experiment-selector-modal.css (8b)', () => {
@@ -21,22 +21,31 @@ describe('menus and app shell styles', () => {
21
21
  expect(css('step-wizard.css')).not.toMatch(/\.mint-wizard__nav-btn/)
22
22
  })
23
23
 
24
- it('should pad the top bar to 52px around 32px controls (5a)', () => {
25
- expect(css('app-top-bar.css')).toMatch(/\.mint-topbar\s*\{[^}]*padding:\s*0\.625rem 0\.75rem;/)
24
+ it('should pad the top bar to the 1.2 height (62px)', () => {
25
+ expect(css('app-top-bar.css')).toMatch(/\.mint-topbar\s*\{[^}]*padding:\s*0\.75rem 1rem;/)
26
+ })
27
+
28
+ // 28px items + 3px track padding + 1px border on each side = the 36px top-bar control height.
29
+ it('should size pill nav items so the track matches the 36px top-bar controls', () => {
30
+ expect(css('app-pill-nav.css')).toMatch(/\.mint-pill-nav__item\s*\{[^}]*height:\s*1\.75rem;/)
31
+ })
32
+
33
+ it('should size the page selector trigger to the 36px identity tile', () => {
34
+ expect(css('app-page-selector.css')).toMatch(/\.mint-page-selector__trigger\s*\{[^}]*height:\s*2\.25rem;/)
26
35
  })
27
36
 
28
37
  // Shrinking the tile beside the identity also shrank structured icons inside their solid chip, drawing a ring.
29
- it('should keep the 32px plugin tile beside the static plugin identity (5a)', () => {
38
+ it('should keep the plugin tile beside the static plugin identity (5a)', () => {
30
39
  expect(css('app-top-bar.css')).not.toMatch(/:has\(\+ \.mint-topbar__plugin-identity\)/)
31
40
  })
32
41
 
33
42
  // A slotted 36px logo shrank to 32px wide but kept its height, drawing a 32x36 tile.
34
- it('should fit slotted identity content inside the 32px tile (5a)', () => {
43
+ it('should fit slotted identity content inside the identity tile (5a)', () => {
35
44
  expect(css('app-top-bar.css')).toMatch(/\.mint-topbar__identity > \*\s*\{[^}]*max-width:\s*100%;[^}]*max-height:\s*100%;/)
36
45
  })
37
46
 
38
47
  // 15px x 1.15 + 12px x 1.2 = 31.7px, so the title group fits the 32px control row.
39
- it('should fit the top-bar title and subtitle inside the 32px control row (5a)', () => {
48
+ it('should fit the top-bar title and subtitle inside the control row (5a)', () => {
40
49
  expect(css('app-top-bar.css')).toMatch(/\.mint-topbar-title\s*\{[^}]*line-height:\s*1\.15;/)
41
50
  })
42
51
 
@@ -444,6 +444,7 @@ const effectiveCurrentPageSelectorId = computed(() =>
444
444
  :title="settingsConfig?.title"
445
445
  :tabs="normalizedSettingsTabs"
446
446
  :show-appearance="settingsConfig?.showAppearance ?? true"
447
+ :appearance-first="settingsConfig?.appearanceFirst"
447
448
  :size="settingsConfig?.size"
448
449
  :layout="settingsConfig?.layout"
449
450
  :schema="settingsConfig?.schema"
@@ -14,6 +14,7 @@ import { useExperimentStore } from '../stores/experiment'
14
14
  import {
15
15
  formatExperimentDate,
16
16
  formatExperimentStatus,
17
+ getExperimentStatusVariant,
17
18
  EXPERIMENT_STATUS_OPTIONS,
18
19
  DATE_PRESET_OPTIONS,
19
20
  SORT_OPTIONS,
@@ -105,8 +106,10 @@ function describe(experiment: ExperimentSummary) {
105
106
  meta: [
106
107
  ...(groupToggle.value ? [] : [experiment.project_name || experiment.project || '']),
107
108
  owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : '',
108
- formatExperimentStatus(experiment.status),
109
109
  ].filter(Boolean),
110
+ status: experiment.status
111
+ ? { label: formatExperimentStatus(experiment.status), variant: getExperimentStatusVariant(experiment.status) }
112
+ : undefined,
110
113
  date: formatExperimentDate(experiment.created_at),
111
114
  }
112
115
  }
@@ -68,7 +68,9 @@ export function normalizeVisibleSettingsTabs(
68
68
 
69
69
  export function buildSettingsTabs(
70
70
  baseTabs: SettingsTab[],
71
- showAppearance: boolean
71
+ showAppearance: boolean,
72
+ appearanceFirst = false
72
73
  ): SettingsTab[] {
73
- return showAppearance ? [...baseTabs, APPEARANCE_TAB] : baseTabs
74
+ if (!showAppearance) return baseTabs
75
+ return appearanceFirst ? [APPEARANCE_TAB, ...baseTabs] : [...baseTabs, APPEARANCE_TAB]
74
76
  }
@@ -90,6 +90,8 @@ interface Props {
90
90
  /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */
91
91
  tabs?: SettingsTabInput[]
92
92
  showAppearance?: boolean
93
+ /** List Appearance before the other tabs and open on it. */
94
+ appearanceFirst?: boolean
93
95
  size?: 'md' | 'lg' | 'xl'
94
96
  layout?: SettingsModalLayout
95
97
  /** Declarative schema — fields auto-render via SDK form components. */
@@ -238,7 +240,7 @@ const allTabs = computed<SettingsTab[]>(() => {
238
240
  const base: SettingsTab[] = settingsSchema.value
239
241
  ? visibleSchemaGroups.value.map(settingsGroupToTab)
240
242
  : manualTabs.value
241
- return buildSettingsTabs(base, props.showAppearance)
243
+ return buildSettingsTabs(base, props.showAppearance, props.appearanceFirst)
242
244
  })
243
245
 
244
246
  const activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)
@@ -272,7 +274,7 @@ const modalTabs = computed<ModalTab[]>(() =>
272
274
  description: tab.description,
273
275
  meta: group ? (modified ? String(modified) : undefined) : tab.meta,
274
276
  // Sections only split plugin groups from Appearance; without it there is nothing to split.
275
- section: props.showAppearance ? (tab.id === APPEARANCE_TAB_ID ? 'Personal' : 'Plugin') : undefined,
277
+ section: props.showAppearance ? (tab.id === APPEARANCE_TAB_ID ? 'Personal' : tab.section ?? 'Plugin') : undefined,
276
278
  }
277
279
  }),
278
280
  )
@@ -1,21 +1,25 @@
1
1
  <script setup lang="ts" generic="T">
2
2
  /**
3
3
  * Searchable, optionally grouped single-select list shared by the experiment and
4
- * artifact selector modals (8b): 44px two-line rows, trailing check, skeleton,
4
+ * artifact selector modals: two-line rows (title, then code + meta) with status and date
5
+ * in a right-hand column, a filled selected row, skeleton,
5
6
  * error and empty states, and the combobox keyboard model (↑ ↓ Enter).
6
7
  */
7
8
  import { computed, nextTick, reactive, ref, useId, watch } from 'vue'
8
9
  import BaseInput from '../BaseInput.vue'
9
10
  import Skeleton from '../Skeleton.vue'
10
11
  import EmptyState from '../EmptyState.vue'
12
+ import type { PillVariant } from '../../types'
11
13
 
12
14
  export interface ResourcePickerRow {
13
- /** Identifier shown in mono before the title, e.g. an experiment code. */
15
+ /** Identifier shown as a gray pill at the start of the second line, e.g. an experiment code. */
14
16
  code?: string
15
17
  title: string
16
- /** Second-line facts, joined with " · ". */
18
+ /** Second-line facts after the code, joined with " · ". */
17
19
  meta?: string[]
18
- /** Rendered last on the second line, in mono. */
20
+ /** Right column, above the date, tinted by its variant. */
21
+ status?: { label: string; variant: PillVariant }
22
+ /** Right column, in mono. */
19
23
  date?: string
20
24
  }
21
25
 
@@ -206,36 +210,26 @@ watch(
206
210
  @mouseenter="activeIndex = indexOf(item)"
207
211
  >
208
212
  <span class="mint-resource-picker__text">
209
- <span class="mint-resource-picker__title"
210
- ><template v-if="describe(item).code"
211
- ><span class="mint-resource-picker__code">{{ describe(item).code }}</span> · </template
212
- >{{ describe(item).title }}</span
213
+ <span class="mint-resource-picker__title">{{ describe(item).title }}</span>
214
+ <span v-if="describe(item).code || describe(item).meta?.length" class="mint-resource-picker__meta"
215
+ ><span v-if="describe(item).code" class="mint-resource-picker__code mint-exp-code mint-exp-code--sm">{{
216
+ describe(item).code
217
+ }}</span
218
+ ><span v-if="describe(item).meta?.length">{{ describe(item).meta!.join(' · ') }}</span></span
213
219
  >
220
+ </span>
221
+ <span v-if="describe(item).status || describe(item).date" class="mint-resource-picker__aside">
214
222
  <span
215
- v-if="describe(item).meta?.length || describe(item).date"
216
- class="mint-resource-picker__meta"
217
- >{{ [...(describe(item).meta ?? [])].join(' · ')
218
- }}<template v-if="describe(item).date"
219
- >{{ describe(item).meta?.length ? ' · ' : ''
220
- }}<span class="mint-resource-picker__date">{{ describe(item).date }}</span></template
221
- ></span
223
+ v-if="describe(item).status"
224
+ class="mint-resource-picker__status"
225
+ :class="`mint-resource-picker__status--${describe(item).status!.variant}`"
226
+ >{{ describe(item).status!.label }}</span
222
227
  >
228
+ <span v-if="describe(item).date" class="mint-resource-picker__date">{{ describe(item).date }}</span>
223
229
  </span>
224
230
  <span v-if="$slots.actions" class="mint-resource-picker__actions" @click.stop>
225
231
  <slot name="actions" :item="item" />
226
232
  </span>
227
- <span class="mint-resource-picker__check-slot" aria-hidden="true">
228
- <svg
229
- v-if="itemKey(item) === selectedKey"
230
- class="mint-resource-picker__check"
231
- viewBox="0 0 16 16"
232
- fill="none"
233
- stroke="currentColor"
234
- stroke-width="2"
235
- >
236
- <path d="M3.5 8.5l3 3 6-7" />
237
- </svg>
238
- </span>
239
233
  </div>
240
234
  </template>
241
235
  </div>
@@ -1,5 +1,5 @@
1
1
  /* AppTopBar page selector — left-side route/page dropdown.
2
- Trigger speaks the field language (5a): 32px, hover changes the background
2
+ Trigger speaks the field language (5a): 36px (the identity tile height), hover changes the background
3
3
  only, open = field focus, inline 12px chevron. Menu = .mint-popover +
4
4
  .mint-menu-item (2d); __menu / __item stay as aliases. */
5
5
 
@@ -13,8 +13,8 @@
13
13
  align-items: center;
14
14
  gap: 0.5rem;
15
15
  width: 100%;
16
- height: 2rem;
17
- padding: 0 0.5rem 0 0.3125rem;
16
+ height: 2.25rem;
17
+ padding: 0 0.5rem 0 0.4375rem;
18
18
  border: 1px solid var(--border-color);
19
19
  background: var(--bg-card);
20
20
  color: var(--text-primary);
@@ -21,7 +21,7 @@
21
21
  display: inline-flex;
22
22
  align-items: center;
23
23
  gap: 0.375rem;
24
- height: 1.625rem;
24
+ height: 1.75rem;
25
25
  border: 0;
26
26
  padding: 0 0.875rem;
27
27
  border-radius: 9999px;
@@ -9,7 +9,7 @@
9
9
  .mint-topbar {
10
10
  display: block;
11
11
  width: 100%;
12
- padding: 0.625rem 0.75rem;
12
+ padding: 0.75rem 1rem;
13
13
  background-color: var(--bg-secondary);
14
14
  box-sizing: border-box;
15
15
  }
@@ -19,7 +19,7 @@
19
19
  display: flex;
20
20
  align-items: center;
21
21
  gap: 0.5rem;
22
- min-height: 2rem;
22
+ min-height: 2.375rem;
23
23
  position: relative;
24
24
  }
25
25
 
@@ -31,24 +31,28 @@
31
31
  }
32
32
 
33
33
  .mint-topbar__identity {
34
- width: 2rem;
35
- height: 2rem;
34
+ width: 2.25rem;
35
+ height: 2.25rem;
36
36
  display: inline-flex;
37
37
  align-items: center;
38
38
  justify-content: center;
39
39
  flex-shrink: 0;
40
40
  }
41
41
 
42
- /* Slotted logos sized above 32px shrink to fit instead of turning into a 32x36 tile. */
42
+ /* Slotted logos sized above 36px shrink to fit instead of stretching the tile. */
43
43
  .mint-topbar__identity > * {
44
44
  max-width: 100%;
45
45
  max-height: 100%;
46
46
  }
47
47
 
48
- /* The 36px plugin tile would push the bar past 52px; the bar uses the 32px control height. */
49
- .mint-topbar__identity .mint-plugin-icon--topbar {
50
- width: 2rem;
51
- height: 2rem;
48
+ /* Left and right fields share the 36px identity tile height. */
49
+ .mint-topbar .mint-experiment-popover__trigger,
50
+ .mint-topbar .mint-experiment-popover__save-trigger {
51
+ height: 2.25rem;
52
+ }
53
+
54
+ .mint-topbar .mint-experiment-popover__save-trigger {
55
+ width: 2.25rem;
52
56
  }
53
57
 
54
58
  /* Centered section — absolute so asymmetric left/right weight never shifts the
@@ -87,8 +91,8 @@
87
91
  }
88
92
 
89
93
  .mint-topbar__logo-icon {
90
- width: 2rem;
91
- height: 2rem;
94
+ width: 2.25rem;
95
+ height: 2.25rem;
92
96
  background-color: var(--color-primary);
93
97
  border-radius: var(--radius-md);
94
98
  display: flex;
@@ -151,7 +155,7 @@
151
155
  opacity: 0.8;
152
156
  }
153
157
 
154
- /* Title + subtitle stay inside the 32px control row (5a: 52px bar). */
158
+ /* Title + subtitle stay inside the 36px control row. */
155
159
  .mint-topbar-title-group {
156
160
  display: flex;
157
161
  flex-direction: column;
@@ -5,6 +5,7 @@
5
5
  align-items: center;
6
6
  gap: 0.5rem;
7
7
  flex-wrap: wrap;
8
+ padding: 0 1.25rem;
8
9
  }
9
10
 
10
11
  .mint-experiment-selector__filter-select {
@@ -65,14 +66,6 @@ html.dark .mint-experiment-selector__filters-toggle--active {
65
66
  flex-shrink: 0;
66
67
  }
67
68
 
68
- /* Advanced filters row */
69
- .mint-experiment-selector__filters-advanced {
70
- padding: 0.625rem;
71
- border: 1px solid var(--border-light);
72
- border-radius: var(--radius-md);
73
- background-color: color-mix(in srgb, var(--bg-secondary) 72%, transparent);
74
- }
75
-
76
69
  /* Group toggle checkbox label */
77
70
  .mint-experiment-selector__group-toggle {
78
71
  display: inline-flex;
@@ -103,7 +96,8 @@ html.dark .mint-experiment-selector__filters-toggle--active {
103
96
  .mint-experiment-selector__footer {
104
97
  display: flex;
105
98
  justify-content: flex-start;
106
- padding-top: 0.25rem;
99
+ padding: 0.5rem 1.25rem 0.75rem;
100
+ border-top: 1px solid var(--border-light);
107
101
  }
108
102
 
109
103
  .mint-experiment-selector__clear-btn {
@@ -1,4 +1,16 @@
1
- /* ResourcePickerList (8b): shared list for ExperimentSelectorModal and ArtifactSelectorModal. */
1
+ /* ResourcePickerList: shared list for ExperimentSelectorModal and ArtifactSelectorModal.
2
+ No frame of its own (the modal is the card); rows are inset and rounded, groups are
3
+ pinned bars. */
4
+
5
+ /* The modal is the only card: the list runs edge to edge, and the toolbar, group
6
+ bars and row text keep the modal's 1.25rem gutter themselves. */
7
+ .mint-modal__body:has(> .mint-resource-picker) {
8
+ padding: 0;
9
+ }
10
+ .mint-modal__container:has(.mint-resource-picker) .mint-modal__header {
11
+ border-bottom: none;
12
+ padding-bottom: 0.75rem;
13
+ }
2
14
 
3
15
  .mint-resource-picker {
4
16
  display: flex;
@@ -11,6 +23,7 @@
11
23
  align-items: center;
12
24
  flex-wrap: wrap;
13
25
  gap: 0.5rem;
26
+ padding: 0 1.25rem;
14
27
  }
15
28
  .mint-resource-picker__search {
16
29
  flex: 1;
@@ -20,12 +33,10 @@
20
33
  .mint-resource-picker__frame {
21
34
  display: flex;
22
35
  flex-direction: column;
23
- overflow: hidden;
24
- border: 1px solid var(--border-color);
25
- border-radius: var(--radius-md);
26
- background-color: var(--bg-card);
36
+ border-top: 1px solid var(--border-color);
27
37
  }
28
38
  .mint-resource-picker__list {
39
+ padding-bottom: 0.5rem;
29
40
  max-height: min(28rem, calc(100vh - 16rem));
30
41
  overflow-y: auto;
31
42
  overscroll-behavior: contain;
@@ -38,14 +49,17 @@
38
49
  gap: 0.5rem;
39
50
  flex-shrink: 0;
40
51
  width: 100%;
52
+ position: sticky;
53
+ top: 0;
54
+ z-index: 1;
41
55
  height: 30px;
42
- padding: 0 12px;
56
+ padding: 0 1.25rem;
43
57
  border: 0;
44
58
  border-bottom: 1px solid var(--border-light);
45
59
  background-color: var(--bg-primary);
46
60
  font-family: inherit;
47
61
  font-size: 12px;
48
- font-weight: 500;
62
+ font-weight: 600;
49
63
  color: var(--text-secondary);
50
64
  text-align: left;
51
65
  cursor: pointer;
@@ -77,24 +91,25 @@
77
91
  .mint-resource-picker__group-count {
78
92
  font: 11px var(--font-mono);
79
93
  font-variant-numeric: tabular-nums;
94
+ color: var(--text-muted);
80
95
  }
81
96
 
82
97
  .mint-resource-picker__row {
83
98
  display: grid;
84
- grid-template-columns: minmax(0, 1fr) auto 14px;
99
+ grid-template-columns: minmax(0, 1fr);
100
+ grid-auto-flow: column;
101
+ grid-auto-columns: auto;
85
102
  align-items: center;
86
- gap: 10px;
103
+ gap: 12px;
87
104
  box-sizing: border-box;
88
105
  flex-shrink: 0;
89
- min-height: 44px;
90
- padding: 5px 12px;
91
- border-bottom: 1px solid var(--border-light);
106
+ min-height: 56px;
107
+ margin: 2px 0.5rem;
108
+ padding: 10px 12px;
109
+ border-radius: var(--radius-md);
92
110
  cursor: pointer;
93
111
  transition: background-color 150ms ease;
94
112
  }
95
- .mint-resource-picker__row:last-child {
96
- border-bottom: 0;
97
- }
98
113
  .mint-resource-picker__row:hover {
99
114
  background-color: var(--bg-hover);
100
115
  }
@@ -110,7 +125,7 @@
110
125
  .mint-resource-picker__text {
111
126
  display: flex;
112
127
  flex-direction: column;
113
- gap: 1px;
128
+ gap: 4px;
114
129
  min-width: 0;
115
130
  line-height: 1.25;
116
131
  }
@@ -118,18 +133,49 @@
118
133
  overflow: hidden;
119
134
  text-overflow: ellipsis;
120
135
  white-space: nowrap;
121
- font-size: 13px;
122
- font-weight: 500;
136
+ font-size: 15px;
137
+ font-weight: 600;
123
138
  color: var(--text-primary);
124
139
  }
125
140
  .mint-resource-picker__row--selected .mint-resource-picker__title {
126
141
  color: var(--_ink-primary);
127
142
  }
128
- .mint-resource-picker__code,
143
+ .mint-resource-picker__code {
144
+ margin-right: 0.5rem;
145
+ }
146
+ .mint-resource-picker__aside {
147
+ display: flex;
148
+ flex-direction: column;
149
+ align-items: flex-end;
150
+ gap: 4px;
151
+ line-height: 1.25;
152
+ white-space: nowrap;
153
+ }
129
154
  .mint-resource-picker__date {
130
- font-family: var(--font-mono);
155
+ font: 12px var(--font-mono);
131
156
  font-variant-numeric: tabular-nums;
157
+ color: var(--text-muted);
158
+ }
159
+ .mint-resource-picker__status {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ gap: 5px;
163
+ font-size: 12px;
164
+ font-weight: 500;
165
+ color: var(--text-secondary);
132
166
  }
167
+ .mint-resource-picker__status::before {
168
+ content: '';
169
+ width: 6px;
170
+ height: 6px;
171
+ border-radius: 50%;
172
+ background-color: currentColor;
173
+ }
174
+ .mint-resource-picker__status--primary { color: var(--_ink-primary); }
175
+ .mint-resource-picker__status--success { color: var(--mint-success); }
176
+ .mint-resource-picker__status--warning { color: var(--mint-warning); }
177
+ .mint-resource-picker__status--error { color: var(--mint-error); }
178
+ .mint-resource-picker__status--info { color: var(--mint-info); }
133
179
  .mint-resource-picker__meta {
134
180
  overflow: hidden;
135
181
  text-overflow: ellipsis;
@@ -142,29 +188,17 @@
142
188
  align-items: center;
143
189
  gap: 4px;
144
190
  }
145
- .mint-resource-picker__check-slot {
146
- display: flex;
147
- width: 14px;
148
- }
149
- .mint-resource-picker__check {
150
- width: 14px;
151
- height: 14px;
152
- color: var(--color-primary);
153
- }
154
191
 
155
192
  .mint-resource-picker__skeleton-row {
156
193
  display: flex;
157
194
  flex-direction: column;
158
195
  justify-content: center;
159
- gap: 6px;
160
- min-height: 44px;
161
- padding: 5px 12px;
162
- border-bottom: 1px solid var(--border-light);
163
- }
164
- .mint-resource-picker__skeleton-row:last-child {
165
- border-bottom: 0;
196
+ gap: 8px;
197
+ min-height: 56px;
198
+ padding: 10px 1.25rem;
166
199
  }
167
200
  .mint-resource-picker__error {
201
+ margin: 0 1.25rem 1.25rem;
168
202
  padding: 0.75rem;
169
203
  border: 1px solid var(--mint-error-border);
170
204
  border-radius: var(--radius-md);