@morscherlab/mint-sdk 1.5.0-beta.1 → 1.5.0-beta.10

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 (174) hide show
  1. package/dist/{AlertBox-CIhqsWbN.js → AlertBox-Cai0w7gF.js} +3 -3
  2. package/dist/{AlertBox-CIhqsWbN.js.map → AlertBox-Cai0w7gF.js.map} +1 -1
  3. package/dist/{AppSidebar-BDrd7Kwn.js → AppSidebar-C-Vq5x4I.js} +410 -127
  4. package/dist/AppSidebar-C-Vq5x4I.js.map +1 -0
  5. package/dist/{ConfirmDialog-CjWoxAD1.js → ConfirmDialog-BjFsOpBe.js} +3 -3
  6. package/dist/ConfirmDialog-BjFsOpBe.js.map +1 -0
  7. package/dist/{ExperimentPopover-BM92LlrQ.js → ExperimentPopover-DYy_s0kI.js} +25 -37
  8. package/dist/ExperimentPopover-DYy_s0kI.js.map +1 -0
  9. package/dist/{ExperimentSelectorModal-LsB1ABmh.js → ExperimentSelectorModal-Bfh5bdkv.js} +37 -8
  10. package/dist/ExperimentSelectorModal-Bfh5bdkv.js.map +1 -0
  11. package/dist/{SegmentedControl-Dz41vAFM.js → SegmentedControl-B8KgyOjd.js} +2 -2
  12. package/dist/{SegmentedControl-Dz41vAFM.js.map → SegmentedControl-B8KgyOjd.js.map} +1 -1
  13. package/dist/{SettingsModal-C-MSR-bk.js → SettingsModal-DwfrD1ER.js} +7 -7
  14. package/dist/{SettingsModal-C-MSR-bk.js.map → SettingsModal-DwfrD1ER.js.map} +1 -1
  15. package/dist/__tests__/components/AppTopBarCompact.test.d.ts +1 -0
  16. package/dist/__tests__/components/AppTopBarCompactReset.test.d.ts +1 -0
  17. package/dist/__tests__/components/AutoGroupModal.experimentLoad.test.d.ts +1 -0
  18. package/dist/components/AppTopBar.compact.d.ts +5 -0
  19. package/dist/components/AppTopBar.vue.d.ts +17 -17
  20. package/dist/components/FormBuilder.vue.d.ts +1 -1
  21. package/dist/components/PluginIcon.vue.d.ts +2 -2
  22. package/dist/components/PluginWorkspaceView.vue.d.ts +4 -0
  23. package/dist/components/SectionCard.vue.d.ts +13 -1
  24. package/dist/components/SettingsModal.vue.d.ts +1 -1
  25. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -2
  26. package/dist/components/internal/FormSchemaInternal.vue.d.ts +9 -9
  27. package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
  28. package/dist/components/internal/LucideIconInternal.vue.d.ts +17 -0
  29. package/dist/composables/appLayoutSidebar.d.ts +9 -0
  30. package/dist/composables/useExperimentSelector.d.ts +2 -0
  31. package/dist/composables/useOnboarding.d.ts +1 -1
  32. package/dist/composables/usePluginWorkspace.d.ts +2 -0
  33. package/dist/composables/useVersionedSave.d.ts +5 -2
  34. package/dist/{controlSchemaModel-BbXbMgc6.js → controlSchemaModel-BtFNZj8t.js} +2 -2
  35. package/dist/{controlSchemaModel-BbXbMgc6.js.map → controlSchemaModel-BtFNZj8t.js.map} +1 -1
  36. package/dist/{data-LzdkEZJ-.js → data-CJztFns8.js} +31 -15
  37. package/dist/{data-LzdkEZJ-.js.map → data-CJztFns8.js.map} +1 -1
  38. package/dist/data.js +8 -8
  39. package/dist/{experimentStatus-DCn2QKG7.js → experimentStatus-Deokk7Br.js} +2 -2
  40. package/dist/{experimentStatus-DCn2QKG7.js.map → experimentStatus-Deokk7Br.js.map} +1 -1
  41. package/dist/index.d.ts +0 -1
  42. package/dist/index.js +15 -20
  43. package/dist/index.js.map +1 -1
  44. package/dist/{install-UTqrAgHe.js → install-Bk02tJ5M.js} +15 -40
  45. package/dist/install-Bk02tJ5M.js.map +1 -0
  46. package/dist/install.js +1 -1
  47. package/dist/{lab-CbdDLAVr.js → lab-BYhb_cJI.js} +47 -117
  48. package/dist/lab-BYhb_cJI.js.map +1 -0
  49. package/dist/lab.js +4 -4
  50. package/dist/{records-CWRztE5M.js → records-GtiVMlTQ.js} +207 -171
  51. package/dist/records-GtiVMlTQ.js.map +1 -0
  52. package/dist/{runtime-DrL6lKAb.js → runtime-aZPUPxoB.js} +112 -54
  53. package/dist/runtime-aZPUPxoB.js.map +1 -0
  54. package/dist/runtime.js +6 -6
  55. package/dist/{shell-Ch7cJnjo.js → shell-Bk9mD4Lc.js} +5 -5
  56. package/dist/{shell-Ch7cJnjo.js.map → shell-Bk9mD4Lc.js.map} +1 -1
  57. package/dist/shell.js +5 -5
  58. package/dist/styles.css +315 -134
  59. package/dist/styles.d.ts +1 -0
  60. package/dist/types/components.d.ts +22 -2
  61. package/dist/types/generated-ui.d.ts +2 -0
  62. package/dist/types/index.d.ts +2 -2
  63. package/dist/types/instrument.d.ts +5 -17
  64. package/dist/{useDropdownState-uFIz6Yv8.js → useDropdownState-CcBzUYZ6.js} +14 -5
  65. package/dist/useDropdownState-CcBzUYZ6.js.map +1 -0
  66. package/dist/{useExperimentSamples-Cz2v3V_q.js → useExperimentSamples-CpNXvpUn.js} +3 -3
  67. package/dist/{useExperimentSamples-Cz2v3V_q.js.map → useExperimentSamples-CpNXvpUn.js.map} +1 -1
  68. package/dist/{useListReorder-QqJc1dpX.js → useListReorder-Bp4EAI2w.js} +16 -4
  69. package/dist/useListReorder-Bp4EAI2w.js.map +1 -0
  70. package/dist/{useRackEditor-BHGM1Ngz.js → useRackEditor-i2bdPUFf.js} +156 -27
  71. package/dist/useRackEditor-i2bdPUFf.js.map +1 -0
  72. package/dist/{useRealtime-CNQbrS01.js → useRealtime-BLQEjbuq.js} +3 -3
  73. package/dist/{useRealtime-CNQbrS01.js.map → useRealtime-BLQEjbuq.js.map} +1 -1
  74. package/dist/useRequest-CXyPw3eh.js +76 -0
  75. package/dist/useRequest-CXyPw3eh.js.map +1 -0
  76. package/dist/utils/deprecation.d.ts +2 -0
  77. package/dist/utils/lucideIcon.d.ts +4 -0
  78. package/package.json +7 -4
  79. package/src/__tests__/components/AppTopBar.test.ts +102 -20
  80. package/src/__tests__/components/AppTopBarCompact.test.ts +65 -0
  81. package/src/__tests__/components/AppTopBarCompactReset.test.ts +24 -0
  82. package/src/__tests__/components/AppTopBarOnboarding.test.ts +10 -0
  83. package/src/__tests__/components/AutoGroupModal.experimentLoad.test.ts +58 -0
  84. package/src/__tests__/components/BaseModal.test.ts +33 -2
  85. package/src/__tests__/components/ConfirmDialog.test.ts +2 -2
  86. package/src/__tests__/components/ExperimentPopover.test.ts +32 -0
  87. package/src/__tests__/components/ExperimentSelectorModal.test.ts +16 -0
  88. package/src/__tests__/components/PluginWorkspaceView.test.ts +20 -1
  89. package/src/__tests__/components/SectionCard.test.ts +33 -0
  90. package/src/__tests__/components/deprecationTags.test.ts +4 -18
  91. package/src/__tests__/composables/useJobCenter.test.ts +35 -0
  92. package/src/__tests__/composables/useJobsEndpoint.test.ts +50 -0
  93. package/src/__tests__/composables/useOnboarding.test.ts +2 -2
  94. package/src/__tests__/composables/useVersionedSave.test.ts +71 -6
  95. package/src/__tests__/docs/frontendDocsCatalog.test.ts +3 -3
  96. package/src/__tests__/styles/menusShellRefresh.test.ts +10 -0
  97. package/src/components/AppLayout.story.vue +5 -5
  98. package/src/components/AppLayout.vue +13 -1
  99. package/src/components/AppSidebar.vue +2 -2
  100. package/src/components/AppTopBar.compact.ts +60 -0
  101. package/src/components/AppTopBar.story.vue +39 -14
  102. package/src/components/AppTopBar.vue +222 -89
  103. package/src/components/BaseModal.vue +16 -5
  104. package/src/components/CollapsibleCard.vue +1 -1
  105. package/src/components/ConfirmDialog.vue +1 -1
  106. package/src/components/ExperimentPopover.story.vue +2 -2
  107. package/src/components/ExperimentPopover.vue +6 -22
  108. package/src/components/ExperimentSelectorModal.vue +35 -0
  109. package/src/components/FileUploader.vue +3 -35
  110. package/src/components/GeneratedPluginView.vue +34 -81
  111. package/src/components/GeneratedResultRenderer.vue +2 -1
  112. package/src/components/InstrumentStatusPanels.vue +3 -3
  113. package/src/components/PlotlyChart.vue +1 -1
  114. package/src/components/PluginIcon.story.vue +2 -2
  115. package/src/components/PluginIcon.vue +2 -2
  116. package/src/components/PluginWorkspaceView.vue +10 -1
  117. package/src/components/SectionCard.vue +42 -6
  118. package/src/components/internal/AppTopBarOnboardingInternal.vue +30 -14
  119. package/src/components/internal/AppTopBarPageSelectorInternal.vue +12 -0
  120. package/src/components/internal/AutoGroupModalInternal.vue +14 -3
  121. package/src/components/internal/LucideIconInternal.vue +23 -0
  122. package/src/composables/appLayoutSidebar.ts +11 -0
  123. package/src/composables/index.ts +1 -2
  124. package/src/composables/useExperimentSelector.ts +4 -0
  125. package/src/composables/useJobCenter.ts +21 -1
  126. package/src/composables/useJobsEndpoint.ts +15 -4
  127. package/src/composables/useOnboarding.ts +3 -3
  128. package/src/composables/usePluginWorkspace.ts +15 -4
  129. package/src/composables/useVersionedSave.ts +32 -7
  130. package/src/generated-ui/main.ts +3 -1
  131. package/src/index.ts +0 -3
  132. package/src/styles/components/alert-box.css +1 -1
  133. package/src/styles/components/app-layout.css +5 -0
  134. package/src/styles/components/app-sidebar.css +5 -5
  135. package/src/styles/components/app-top-bar.css +130 -9
  136. package/src/styles/components/button.css +3 -3
  137. package/src/styles/components/checkbox.css +1 -1
  138. package/src/styles/components/collapsible-card.css +1 -1
  139. package/src/styles/components/confirm-dialog.css +2 -2
  140. package/src/styles/components/file-uploader.css +3 -3
  141. package/src/styles/components/icon-button.css +3 -3
  142. package/src/styles/components/loading-spinner.css +1 -1
  143. package/src/styles/components/onboarding.css +39 -0
  144. package/src/styles/components/pill.css +1 -1
  145. package/src/styles/components/plugin-icon.css +2 -2
  146. package/src/styles/components/presence-avatars.css +4 -4
  147. package/src/styles/components/progress-bar.css +1 -1
  148. package/src/styles/components/section-card.css +65 -6
  149. package/src/styles/components/sequence-input.css +1 -1
  150. package/src/styles/components/slider.css +1 -1
  151. package/src/styles/components/toggle.css +1 -1
  152. package/src/styles/variables.css +10 -4
  153. package/src/types/components.ts +23 -2
  154. package/src/types/generated-ui.ts +2 -0
  155. package/src/types/index.ts +1 -1
  156. package/src/types/instrument.ts +5 -18
  157. package/src/utils/deprecation.ts +8 -0
  158. package/src/utils/lucideIcon.ts +25 -0
  159. package/src/utils/lucideIconIndex.d.ts +2 -0
  160. package/dist/AppSidebar-BDrd7Kwn.js.map +0 -1
  161. package/dist/ConfirmDialog-CjWoxAD1.js.map +0 -1
  162. package/dist/ExperimentPopover-BM92LlrQ.js.map +0 -1
  163. package/dist/ExperimentSelectorModal-LsB1ABmh.js.map +0 -1
  164. package/dist/deprecatedRootExports.d.ts +0 -191
  165. package/dist/install-UTqrAgHe.js.map +0 -1
  166. package/dist/lab-CbdDLAVr.js.map +0 -1
  167. package/dist/records-CWRztE5M.js.map +0 -1
  168. package/dist/runtime-DrL6lKAb.js.map +0 -1
  169. package/dist/useDropdownState-uFIz6Yv8.js.map +0 -1
  170. package/dist/useListReorder-QqJc1dpX.js.map +0 -1
  171. package/dist/useRackEditor-BHGM1Ngz.js.map +0 -1
  172. package/dist/useRequest-Bfsh7gIm.js +0 -215
  173. package/dist/useRequest-Bfsh7gIm.js.map +0 -1
  174. package/src/deprecatedRootExports.ts +0 -226
@@ -22,10 +22,12 @@ import {
22
22
  type GeneratedAnalysisTransport,
23
23
  } from '../composables/useJobsEndpoint'
24
24
  import AlertBox from './AlertBox.vue'
25
+ import AppContainer from './AppContainer.vue'
25
26
  import EmptyState from './EmptyState.vue'
26
27
  import GeneratedResultRenderer from './GeneratedResultRenderer.vue'
27
28
  import LoadingSpinner from './LoadingSpinner.vue'
28
29
  import PluginWorkspaceView from './PluginWorkspaceView.vue'
30
+ import SectionCard from './SectionCard.vue'
29
31
  import { GENERATED_JOBS_API_KEY } from './internal/generatedJobsApi'
30
32
  import { definePluginWorkspaceView } from '../composables/componentSpecs'
31
33
  import { providePluginIdentity } from '../composables/pluginIdentity'
@@ -48,6 +50,7 @@ const generated = useJobsEndpoint({
48
50
  provide(GENERATED_JOBS_API_KEY, { baseUrl: props.baseUrl, transport: props.transport })
49
51
  const activeDefinitionId = ref('')
50
52
  const selectedJobId = ref('')
53
+ const sidebarCollapsed = ref(false)
51
54
  const valuesByDefinition = reactive<Record<string, Record<string, unknown>>>({})
52
55
  const definitions = computed(() => generated.manifest.value?.jobs ?? [])
53
56
  const activeDefinition = computed(() => (
@@ -77,12 +80,21 @@ const workspace = computed(() => definePluginWorkspaceView({
77
80
  subtitle: generated.manifest.value?.ui.subtitle || generated.manifest.value?.ui.description,
78
81
  // The platform shell renders the toasts around this view.
79
82
  toasts: false,
83
+ floating: true,
80
84
  topBar: {
85
+ variant: 'card',
81
86
  pills: pillNav.value,
82
87
  settings: false,
83
88
  standaloneLabel: props.showStandaloneLabel ? 'Standalone' : false,
84
89
  },
85
- sidebar: { title: 'Parameters', panels: panels.value, forms: forms.value },
90
+ sidebar: {
91
+ title: 'Parameters',
92
+ width: '300px',
93
+ collapsible: true,
94
+ collapsedWidth: '3rem',
95
+ panels: panels.value,
96
+ forms: forms.value,
97
+ },
86
98
  jobs: generated,
87
99
  }))
88
100
 
@@ -160,6 +172,7 @@ onMounted(() => {
160
172
  <template>
161
173
  <PluginWorkspaceView
162
174
  v-model:view="activeDefinitionId"
175
+ v-model:sidebar-collapsed="sidebarCollapsed"
163
176
  :spec="workspace"
164
177
  :values="currentValues"
165
178
  :form-loading="generated.isSubmitting.value"
@@ -168,7 +181,7 @@ onMounted(() => {
168
181
  @form-submit="submitJob"
169
182
  @job-select="selectJob"
170
183
  >
171
- <main class="mint-generated-workspace">
184
+ <AppContainer direction="column" gap="var(--mint-gutter)" class="mint-generated-workspace">
172
185
  <div v-if="generated.isLoading.value" class="mint-generated-workspace__loading">
173
186
  <LoadingSpinner size="lg" />
174
187
  <span>Loading analysis workspace</span>
@@ -177,18 +190,15 @@ onMounted(() => {
177
190
  <template v-else>
178
191
  <AlertBox v-if="generated.error.value" tone="error">{{ generated.error.value }}</AlertBox>
179
192
 
180
- <header v-if="activeDefinition" class="mint-generated-workspace__header">
181
- <div>
182
- <p class="mint-generated-workspace__eyebrow">Analysis result</p>
183
- <h1>{{ activeDefinition.title }}</h1>
184
- <p v-if="activeDefinition.description">{{ activeDefinition.description }}</p>
185
- </div>
186
- <div class="mint-generated-workspace__resources" aria-label="Job resource requirements">
187
- <span>{{ resourceSummary(activeDefinition) }}</span>
188
- </div>
189
- </header>
190
-
191
- <section class="mint-generated-workspace__result">
193
+ <SectionCard :title="activeDefinition?.title ?? 'Result'">
194
+ <template v-if="activeDefinition" #actions>
195
+ <span class="mint-generated-workspace__resources" aria-label="Job resource requirements">
196
+ {{ resourceSummary(activeDefinition) }}
197
+ </span>
198
+ </template>
199
+ <p v-if="activeDefinition?.description" class="mint-generated-workspace__description">
200
+ {{ activeDefinition.description }}
201
+ </p>
192
202
  <GeneratedResultRenderer
193
203
  v-if="currentResult"
194
204
  :result="currentResult"
@@ -199,93 +209,36 @@ onMounted(() => {
199
209
  description="Set the parameters and run the analysis. Jobs continue independently if you switch tabs."
200
210
  size="md"
201
211
  />
202
- </section>
212
+ </SectionCard>
203
213
  </template>
204
- </main>
214
+ </AppContainer>
205
215
  </PluginWorkspaceView>
206
216
  </template>
207
217
 
208
218
  <style scoped>
209
219
  .mint-generated-workspace {
210
- position: relative;
211
220
  min-width: 0;
212
- min-height: calc(100vh - 5rem);
213
- padding: clamp(1rem, 2.5vw, 2.5rem);
214
- background: var(--bg-primary);
221
+ min-height: 0;
222
+ overflow-y: auto;
215
223
  }
216
224
 
217
225
  .mint-generated-workspace__loading {
218
226
  display: flex;
219
- min-height: 24rem;
227
+ flex: 1;
220
228
  align-items: center;
221
229
  justify-content: center;
222
230
  gap: 0.75rem;
223
231
  color: var(--text-secondary);
224
232
  }
225
233
 
226
- .mint-generated-workspace__header {
227
- display: flex;
228
- align-items: flex-start;
229
- justify-content: space-between;
230
- gap: 1.5rem;
231
- max-width: 76rem;
232
- margin: 0 auto 1.25rem;
233
- }
234
-
235
- .mint-generated-workspace__header h1 {
236
- margin: 0;
237
- color: var(--text-primary);
238
- font-size: clamp(1.35rem, 2vw, 1.8rem);
239
- line-height: 1.2;
240
- }
241
-
242
- .mint-generated-workspace__header p:not(.mint-generated-workspace__eyebrow) {
243
- margin: 0.4rem 0 0;
244
- color: var(--text-secondary);
245
- }
246
-
247
- .mint-generated-workspace__eyebrow {
248
- margin: 0 0 0.35rem;
249
- color: var(--text-secondary);
250
- font-size: 0.72rem;
251
- font-weight: 700;
252
- }
253
-
254
234
  .mint-generated-workspace__resources {
255
- display: flex;
256
- flex-wrap: wrap;
257
- justify-content: flex-end;
258
- gap: 0.4rem;
259
- }
260
-
261
- .mint-generated-workspace__resources span {
262
- padding: 0.3rem 0.55rem;
263
- border: 1px solid var(--border-color);
264
- border-radius: 999px;
265
- background: var(--bg-secondary);
266
- color: var(--text-secondary);
235
+ color: var(--text-muted);
236
+ font-family: var(--font-mono);
267
237
  font-size: 0.75rem;
268
- font-weight: 600;
269
238
  }
270
239
 
271
- .mint-generated-workspace__result {
272
- max-width: 76rem;
273
- min-height: 24rem;
274
- margin: 0 auto;
275
- padding: clamp(1rem, 2vw, 1.75rem);
276
- border: 1px solid var(--border-color);
277
- border-radius: 1rem;
278
- background: var(--bg-secondary);
279
- box-shadow: var(--shadow-sm);
280
- }
281
-
282
- @media (max-width: 640px) {
283
- .mint-generated-workspace__header {
284
- flex-direction: column;
285
- }
286
-
287
- .mint-generated-workspace__resources {
288
- justify-content: flex-start;
289
- }
240
+ .mint-generated-workspace__description {
241
+ margin: 0 0 var(--mint-gutter);
242
+ color: var(--text-secondary);
290
243
  }
291
244
  </style>
@@ -133,6 +133,7 @@ function humanize(value: string): string {
133
133
  padding: 0.75rem 1rem;
134
134
  border-radius: var(--radius-md);
135
135
  background: var(--bg-tertiary);
136
+ font-family: var(--font-mono);
136
137
  font-size: 0.8125rem;
137
138
  line-height: 1.55;
138
139
  white-space: pre-wrap;
@@ -152,7 +153,7 @@ function humanize(value: string): string {
152
153
  gap: 1rem;
153
154
  padding: 1rem;
154
155
  border: 1px solid var(--border-color);
155
- border-radius: 0.75rem;
156
+ border-radius: var(--radius-lg);
156
157
  color: var(--color-primary);
157
158
  text-decoration: none;
158
159
  }
@@ -105,11 +105,11 @@ function formatValue(value: string | number | boolean | null): string {
105
105
  </template>
106
106
 
107
107
  <style scoped>
108
- .mint-status-panels__tabs { padding: 0 1rem; }
109
- .mint-status-panels__body { padding: 0.75rem 1rem; }
108
+ .mint-status-panels__tabs { padding: 0 var(--mint-section-card-inset); }
109
+ .mint-status-panels__body { padding: 0.75rem var(--mint-section-card-inset); }
110
110
  .mint-status-panels__bar { display: flex; flex-direction: column; gap: 0.5rem; }
111
111
  .mint-status-panels__series { align-self: flex-end; }
112
- .mint-status-panels__kv { margin: 0; padding: 0.25rem 1rem 0.5rem; }
112
+ .mint-status-panels__kv { margin: 0; padding: 0.25rem var(--mint-section-card-inset) 0.5rem; }
113
113
  .mint-status-panels__kv-row {
114
114
  display: flex;
115
115
  align-items: baseline;
@@ -360,7 +360,7 @@ onBeforeUnmount(() => {
360
360
  margin: 0;
361
361
  padding: 1rem;
362
362
  border: 1px solid var(--mint-error-border);
363
- border-radius: 0.75rem;
363
+ border-radius: var(--radius-lg);
364
364
  background: var(--mint-error-bg);
365
365
  color: var(--mint-error-hover);
366
366
  }
@@ -52,8 +52,8 @@ function initState() {
52
52
  <HstText v-model="state.icon" title="icon" />
53
53
  <HstSelect v-model="state.size" title="size" :options="['sm', 'md', 'topbar', 'lg']" />
54
54
  <HstSelect v-model="state.variant" title="variant" :options="['solid', 'tinted']" />
55
- <HstSelect v-model="state.tone" title="tone (deprecated, no effect; removed in 1.5)" :options="['', 'neutral', 'primary', 'cta', 'success', 'warning', 'error', 'info']" />
56
- <HstText v-model="state.color" title="color (deprecated, no effect; removed in 1.5)" />
55
+ <HstSelect v-model="state.tone" title="tone (deprecated, no effect; removed in 2.0)" :options="['', 'neutral', 'primary', 'cta', 'success', 'warning', 'error', 'info']" />
56
+ <HstText v-model="state.color" title="color (deprecated, no effect; removed in 2.0)" />
57
57
  </template>
58
58
  </Variant>
59
59
 
@@ -18,9 +18,9 @@ interface Props {
18
18
  icon?: string
19
19
  size?: 'sm' | 'md' | 'topbar' | 'lg'
20
20
  variant?: 'solid' | 'tinted'
21
- /** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the tone has no visible effect. Removed in 1.5. */
21
+ /** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the tone has no visible effect. Removed in 2.0. */
22
22
  tone?: Tone
23
- /** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the color has no visible effect. Removed in 1.5. */
23
+ /** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the color has no visible effect. Removed in 2.0. */
24
24
  color?: string
25
25
  }
26
26
 
@@ -24,6 +24,7 @@ import type { JobState } from '../types/jobs'
24
24
  import { useAppExperiment } from '../composables/useAppExperiment'
25
25
  import { useJobCenter } from '../composables/useJobCenter'
26
26
  import { usePluginWorkspace } from '../composables/usePluginWorkspace'
27
+ import { warnDeprecatedOnce } from '../utils/deprecation'
27
28
  import AppToastContainer from './AppToastContainer.vue'
28
29
  import JobsStatusTray from './JobsStatusTray.vue'
29
30
  import WorkspaceControlsInternal from './internal/WorkspaceControlsInternal.vue'
@@ -51,12 +52,16 @@ const emit = defineEmits<PluginWorkspaceViewEmits>()
51
52
 
52
53
  defineSlots<{
53
54
  default?: (props: PluginWorkspaceSlotProps) => unknown
55
+ /** @deprecated Configure the bar with `spec.topBar` and page icons; removed in 2.0. */
54
56
  topbar?: (props: PluginWorkspaceSlotProps) => unknown
55
57
  sidebar?: (props: PluginWorkspaceSlotProps) => unknown
56
58
  icon?: (props: PluginWorkspaceForwardedSlotProps) => unknown
57
59
  logo?: (props: PluginWorkspaceForwardedSlotProps) => unknown
60
+ /** @deprecated Removed in 2.0. */
58
61
  nav?: (props: PluginWorkspaceForwardedSlotProps) => unknown
62
+ /** @deprecated Removed in 2.0. */
59
63
  center?: (props: PluginWorkspaceForwardedSlotProps) => unknown
64
+ /** @deprecated Use `spec.topBar.actions`; removed in 2.0. */
60
65
  actions?: (props: PluginWorkspaceForwardedSlotProps) => unknown
61
66
  'settings-appearance'?: (props: PluginWorkspaceForwardedSlotProps) => unknown
62
67
  header?: (props: PluginWorkspaceForwardedSlotProps) => unknown
@@ -66,6 +71,9 @@ defineSlots<{
66
71
  }>()
67
72
 
68
73
  const slots = useSlots()
74
+ if (slots.topbar) {
75
+ warnDeprecatedOnce('workspace.slot.topbar', 'PluginWorkspaceView #topbar is deprecated: configure spec.topBar and give pages icons. It is removed in 2.0.')
76
+ }
69
77
 
70
78
  // The experiment chip is setup-time structure, like the rest of the spec.
71
79
  if (props.spec.experiment) {
@@ -93,6 +101,7 @@ const {
93
101
  controls,
94
102
  handleControlValuesUpdate,
95
103
  handlePillSelect,
104
+ handleSidebarCollapsedUpdate,
96
105
  } = usePluginWorkspace({
97
106
  props,
98
107
  slots,
@@ -146,7 +155,7 @@ function handleFormCancel(sectionId: string) {
146
155
  <WorkspaceSidebarInternal
147
156
  :sidebar="sidebar"
148
157
  @update:values="handleControlValuesUpdate"
149
- @update:collapsed="emit('update:sidebarCollapsed', $event)"
158
+ @update:collapsed="handleSidebarCollapsedUpdate"
150
159
  @update:toggle="handleToggle"
151
160
  @section-action="(sectionId: string, actionId: string) => emit('section-action', sectionId, actionId)"
152
161
  @form-submit="handleFormSubmit"
@@ -2,7 +2,7 @@
2
2
  /** One-card page region from the MINT 1.1 card system: 12px radius, the
3
3
  * single --card-shadow token, a header strip (icon chip, title, mono count,
4
4
  * actions), and hairline-separated content — no card-in-card. */
5
- import { computed } from 'vue'
5
+ import { computed, useId } from 'vue'
6
6
  import type { SectionCardAccent, SectionCardDivider } from '../types'
7
7
 
8
8
  interface Props {
@@ -23,6 +23,8 @@ interface Props {
23
23
  * definite height (a flex or grid track).
24
24
  */
25
25
  fill?: boolean
26
+ /** The title toggles the body; bind `v-model:collapsed` to control or keep the state. */
27
+ collapsible?: boolean
26
28
  }
27
29
 
28
30
  const props = withDefaults(defineProps<Props>(), {
@@ -32,8 +34,14 @@ const props = withDefaults(defineProps<Props>(), {
32
34
  uppercase: false,
33
35
  divider: 'strong',
34
36
  fill: false,
37
+ collapsible: false,
35
38
  })
36
39
 
40
+ const collapsed = defineModel<boolean>('collapsed', { default: false })
41
+ const bodyId = useId()
42
+ /** `collapsed` hides content only on a card that also shows the toggle to open it again. */
43
+ const hidden = computed(() => props.collapsible && collapsed.value)
44
+
37
45
  const iconPaths = computed(() =>
38
46
  props.icon === undefined ? [] : Array.isArray(props.icon) ? props.icon : [props.icon],
39
47
  )
@@ -41,6 +49,7 @@ const iconPaths = computed(() =>
41
49
  const headerClasses = computed(() => [
42
50
  'mint-section-card__header',
43
51
  props.divider === 'light' ? 'mint-section-card__header--divider-light' : '',
52
+ hidden.value ? 'mint-section-card__header--collapsed' : '',
44
53
  ])
45
54
 
46
55
  const titleClasses = computed(() => [
@@ -51,6 +60,7 @@ const titleClasses = computed(() => [
51
60
  const cardClasses = computed(() => [
52
61
  'mint-section-card',
53
62
  props.fill ? 'mint-section-card--fill' : '',
63
+ hidden.value ? 'mint-section-card--collapsed' : '',
54
64
  ])
55
65
  </script>
56
66
 
@@ -74,20 +84,46 @@ const cardClasses = computed(() => [
74
84
  <path v-for="(d, i) in iconPaths" :key="i" :d="d" />
75
85
  </svg>
76
86
  </span>
77
- <h2 :class="titleClasses">{{ title }}</h2>
78
- <span v-if="count !== undefined" class="mint-section-card__count">{{ count }}</span>
87
+ <!-- Disclosure pattern: the button sits inside the heading, never the reverse. -->
88
+ <h2 :class="titleClasses">
89
+ <button
90
+ v-if="collapsible"
91
+ type="button"
92
+ class="mint-section-card__toggle"
93
+ :aria-expanded="!collapsed"
94
+ :aria-controls="bodyId"
95
+ @click="collapsed = !collapsed"
96
+ >
97
+ {{ title }}
98
+ <span v-if="count !== undefined" class="mint-section-card__count">{{ count }}</span>
99
+ <svg
100
+ class="mint-section-card__chevron"
101
+ viewBox="0 0 24 24"
102
+ fill="none"
103
+ stroke="currentColor"
104
+ stroke-width="2"
105
+ stroke-linecap="round"
106
+ stroke-linejoin="round"
107
+ aria-hidden="true"
108
+ >
109
+ <path d="m6 9 6 6 6-6" />
110
+ </svg>
111
+ </button>
112
+ <template v-else>{{ title }}</template>
113
+ </h2>
114
+ <span v-if="count !== undefined && !collapsible" class="mint-section-card__count">{{ count }}</span>
79
115
  <div class="mint-section-card__spacer"></div>
80
116
  <slot name="actions" />
81
117
  </div>
82
118
  <!-- Controls belong to the card's chrome, so they sit outside the body and
83
119
  stay put while the body scrolls. -->
84
- <div v-if="$slots.toolbar" class="mint-section-card__toolbar">
120
+ <div v-if="$slots.toolbar" v-show="!hidden" class="mint-section-card__toolbar">
85
121
  <slot name="toolbar" />
86
122
  </div>
87
- <div class="mint-section-card__body">
123
+ <div :id="bodyId" v-show="!hidden" class="mint-section-card__body">
88
124
  <slot />
89
125
  </div>
90
- <div v-if="$slots.footer" class="mint-section-card__footer">
126
+ <div v-if="$slots.footer" v-show="!hidden" class="mint-section-card__footer">
91
127
  <slot name="footer" />
92
128
  </div>
93
129
  </section>
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
7
7
  import { autoUpdate, computePosition, offset, shift } from '@floating-ui/dom'
8
- import { Check, CircleHelp, Play } from 'lucide-vue-next'
8
+ import { Check, CircleHelp, Play, X } from 'lucide-vue-next'
9
9
  import { useEventListener } from '../../composables/useEventListener'
10
10
  import type { OnboardingState } from '../../composables/useOnboarding'
11
11
  import OnboardingLayerInternal from './OnboardingLayerInternal.vue'
@@ -50,6 +50,12 @@ watch(open, async (isOpen) => {
50
50
  })
51
51
  onBeforeUnmount(() => stopAutoUpdate?.())
52
52
 
53
+ // The close button removes itself, so focus moves on to Help instead of the page.
54
+ function hideChecklist() {
55
+ props.state.setChecklistHidden(true)
56
+ nextTick(() => helpButton.value?.focus())
57
+ }
58
+
53
59
  function run(action: () => void) {
54
60
  open.value = false
55
61
  action()
@@ -66,19 +72,29 @@ useEventListener(document, 'keydown', (event) => {
66
72
 
67
73
  <template>
68
74
  <div v-if="hasContent" ref="root" class="mint-onboarding-help">
69
- <button
70
- v-if="state.checklistVisible.value"
71
- type="button"
72
- class="mint-onboarding-help__chip"
73
- :class="{ 'mint-onboarding-help__chip--open': open }"
74
- data-onboarding="topbar-checklist"
75
- :aria-expanded="open"
76
- @click="toggle"
77
- >
78
- Getting started
79
- <span class="mint-onboarding-help__count">{{ state.tasksDone.value }}/{{ state.tasks.value.length }}</span>
80
- <span class="mint-onboarding-help__bar" aria-hidden="true"><span :style="{ width: `${progress}%` }" /></span>
81
- </button>
75
+ <span v-if="state.checklistVisible.value" class="mint-onboarding-help__chip-group">
76
+ <button
77
+ type="button"
78
+ class="mint-onboarding-help__chip"
79
+ :class="{ 'mint-onboarding-help__chip--open': open }"
80
+ data-onboarding="topbar-checklist"
81
+ :aria-expanded="open"
82
+ @click="toggle"
83
+ >
84
+ Getting started
85
+ <span class="mint-onboarding-help__count">{{ state.tasksDone.value }}/{{ state.tasks.value.length }}</span>
86
+ <span class="mint-onboarding-help__bar" aria-hidden="true"><span :style="{ width: `${progress}%` }" /></span>
87
+ </button>
88
+ <button
89
+ type="button"
90
+ class="mint-onboarding-help__chip-close"
91
+ aria-label="Hide checklist"
92
+ title="Hide checklist (Help can show it again)"
93
+ @click="hideChecklist"
94
+ >
95
+ <X :size="12" aria-hidden="true" />
96
+ </button>
97
+ </span>
82
98
  <button
83
99
  ref="helpButton"
84
100
  type="button"
@@ -6,7 +6,9 @@ import { useMenuKeyboard } from '../../composables/useMenuKeyboard'
6
6
  import type { PageSelectorItem, PageSelectorItemInput } from '../../types/components'
7
7
  import { normalizeItemInput } from '../../utils/items'
8
8
  import { isPluginIconFormat } from '../../utils/pluginIcon'
9
+ import { isLucideIconName } from '../../utils/lucideIcon'
9
10
  import ActionItemInternal from './ActionItemInternal.vue'
11
+ import LucideIconInternal from './LucideIconInternal.vue'
10
12
  import PluginIcon from '../PluginIcon.vue'
11
13
 
12
14
  interface Props {
@@ -123,6 +125,11 @@ function handleSelect(page: PageSelectorItem) {
123
125
  size="sm"
124
126
  variant="solid"
125
127
  />
128
+ <LucideIconInternal v-else-if="isLucideIconName(current.icon)" :name="current.icon">
129
+ <template #fallback>
130
+ <span class="mint-page-selector__icon-fallback">{{ current.label.charAt(0) }}</span>
131
+ </template>
132
+ </LucideIconInternal>
126
133
  <span v-else class="mint-page-selector__icon-fallback">{{ current.label.charAt(0) }}</span>
127
134
  </slot>
128
135
  </span>
@@ -180,6 +187,11 @@ function handleSelect(page: PageSelectorItem) {
180
187
  size="sm"
181
188
  variant="solid"
182
189
  />
190
+ <LucideIconInternal v-else-if="isLucideIconName(page.icon)" :name="page.icon">
191
+ <template #fallback>
192
+ <span class="mint-page-selector__icon-fallback">{{ page.label.charAt(0) }}</span>
193
+ </template>
194
+ </LucideIconInternal>
183
195
  <span v-else class="mint-page-selector__icon-fallback">{{ page.label.charAt(0) }}</span>
184
196
  </slot>
185
197
  </span>
@@ -209,8 +209,12 @@ function close() {
209
209
  emit('update:open', false)
210
210
  }
211
211
 
212
+ // A close or a newer fetch makes the response of an earlier fetch obsolete.
213
+ let fetchGeneration = 0
214
+
212
215
  async function fetchExperimentData() {
213
216
  if (!props.experimentId && !props.designData) return
217
+ const generation = ++fetchGeneration
214
218
  autoGroup.inputMode.value = 'experiment'
215
219
  experimentError.value = null
216
220
  experimentLoading.value = true
@@ -221,6 +225,7 @@ async function fetchExperimentData() {
221
225
  } else {
222
226
  const result = await api.get<Record<string, unknown>>(`/experiments/${props.experimentId}/data`)
223
227
  pluginData = result.data as Record<string, unknown> | undefined
228
+ if (generation !== fetchGeneration) return
224
229
  }
225
230
  if (!pluginData || typeof pluginData !== 'object') {
226
231
  experimentError.value = 'No design data found for this experiment'
@@ -232,9 +237,11 @@ async function fetchExperimentData() {
232
237
  }
233
238
  setQcRouting(qcRouting.value)
234
239
  } catch (e) {
235
- experimentError.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'
240
+ if (generation === fetchGeneration) {
241
+ experimentError.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'
242
+ }
236
243
  } finally {
237
- experimentLoading.value = false
244
+ if (generation === fetchGeneration) experimentLoading.value = false
238
245
  }
239
246
  }
240
247
 
@@ -244,7 +251,11 @@ async function fetchExperimentData() {
244
251
  watch(
245
252
  () => props.open,
246
253
  (open) => {
247
- if (!open) return
254
+ if (!open) {
255
+ fetchGeneration += 1
256
+ experimentLoading.value = false
257
+ return
258
+ }
248
259
  activeWorkflow.value = props.initialMode
249
260
  experimentError.value = null
250
261
  qcAsOverlay.value = true
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ /** Internal: renders a Lucide icon by name; shows `fallback` until it loads or when the name is unknown. */
3
+ import { shallowRef, watchEffect, type Component } from 'vue'
4
+ import { loadLucideIcon } from '../../utils/lucideIcon'
5
+
6
+ const props = defineProps<{
7
+ name: string
8
+ size?: number
9
+ }>()
10
+
11
+ const icon = shallowRef<Component>()
12
+ watchEffect(async (onCleanup) => {
13
+ let stale = false
14
+ onCleanup(() => { stale = true })
15
+ const loaded = await loadLucideIcon(props.name)
16
+ if (!stale) icon.value = loaded
17
+ })
18
+ </script>
19
+
20
+ <template>
21
+ <component :is="icon" v-if="icon" :size="size ?? 14" aria-hidden="true" />
22
+ <slot v-else name="fallback" />
23
+ </template>
@@ -0,0 +1,11 @@
1
+ import type { ComputedRef, InjectionKey } from 'vue'
2
+
3
+ /** The responsive sidebar drawer of an AppLayout; its AppTopBar renders the drawer button. */
4
+ export interface AppLayoutSidebarDrawer {
5
+ open: ComputedRef<boolean>
6
+ toggle: () => void
7
+ openLabel: ComputedRef<string>
8
+ closeLabel: ComputedRef<string>
9
+ }
10
+
11
+ export const APP_LAYOUT_SIDEBAR_KEY: InjectionKey<AppLayoutSidebarDrawer> = Symbol('mint-app-layout-sidebar')
@@ -1,6 +1,5 @@
1
1
  // Package-root composables: the L1 and L2 names of spec 3.3. Subpath composables are
2
- // exported from `lab.ts`, `shell.ts`, `data.ts` and `runtime.ts`; `deprecatedRootExports.ts`
3
- // keeps the other 1.3 root names until MINT 1.5.
2
+ // exported from `lab.ts`, `shell.ts`, `data.ts` and `runtime.ts`.
4
3
  export { useTheme } from './useTheme'
5
4
  export { useToast } from './useToast'
6
5
  export { usePlatform } from './usePlatform'
@@ -30,6 +30,8 @@ export interface UseExperimentSelectorOptions {
30
30
  immediate?: boolean
31
31
  /** Keep `statusCounts` in step with the non-status filters (one stats request per change). */
32
32
  withStatusCounts?: boolean
33
+ /** Filter values to start from, for example the ones a caller saved at the last visit. */
34
+ initialFilters?: Partial<ExperimentFilters>
33
35
  }
34
36
 
35
37
  export interface UseExperimentSelectorReturn {
@@ -89,6 +91,7 @@ export function useExperimentSelector(
89
91
  allowedExperimentTypes: optionAllowedExperimentTypes,
90
92
  apiBaseUrl,
91
93
  withStatusCounts = false,
94
+ initialFilters,
92
95
  } = options
93
96
  const platformBase = apiBaseUrl ?? getPlatformApiUrl()
94
97
  const api = useApi()
@@ -124,6 +127,7 @@ export function useExperimentSelector(
124
127
  experimentType: undefined,
125
128
  datePreset: undefined,
126
129
  mine: undefined,
130
+ ...initialFilters,
127
131
  })
128
132
 
129
133
  function normalizeExperimentTypes(values: string[] | null | undefined): string[] | null {