@morscherlab/mint-sdk 1.0.0-beta.3 → 1.0.0-beta.5
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.
- package/README.md +9 -2
- package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
- package/dist/__tests__/composables/useApi.test.d.ts +1 -0
- package/dist/components/AppContainer.vue.d.ts +1 -1
- package/dist/components/AppLayout.vue.d.ts +20 -1
- package/dist/components/AppSidebar.vue.d.ts +57 -5
- package/dist/components/AppTopBar.vue.d.ts +7 -25
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +3 -1
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -0
- package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +5 -0
- package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
- package/dist/components/ControlWorkspaceView.vue.d.ts +24 -7
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
- package/dist/components/FormBuilder.vue.d.ts +9 -9
- package/dist/components/PlateMapEditor.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/WellPlate.vue.d.ts +2 -2
- package/dist/components/index.d.ts +3 -12
- package/dist/components/index.js +3 -3
- package/dist/components/{AppPageSelector.vue.d.ts → internal/AppTopBarPageSelectorInternal.vue.d.ts} +1 -1
- package/dist/components/{AppPillNav.vue.d.ts → internal/AppTopBarPillNavInternal.vue.d.ts} +3 -1
- package/dist/components/{CalendarGridPanel.vue.d.ts → internal/CalendarGridPanelInternal.vue.d.ts} +1 -1
- package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
- package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
- package/dist/{components-D_Sr0adg.js → components-DihbSJjU.js} +5932 -5408
- package/dist/components-DihbSJjU.js.map +1 -0
- package/dist/composables/experiment-utils.d.ts +8 -0
- package/dist/composables/index.d.ts +5 -7
- package/dist/composables/index.js +4 -4
- package/dist/composables/useAppExperiment.d.ts +31 -2
- package/dist/composables/useBioTemplateComponents.d.ts +5 -3
- package/dist/composables/useBioTemplatePackWorkspace.d.ts +3 -2
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +6 -5
- package/dist/composables/useBioTemplateWorkspace.d.ts +5 -4
- package/dist/composables/useControlSchema.d.ts +43 -21
- package/dist/composables/usePluginClient.d.ts +5 -2
- package/dist/{composables-C3dpXQN5.js → composables-BcgZ6diz.js} +40 -28
- package/dist/composables-BcgZ6diz.js.map +1 -0
- package/dist/index.d.ts +5 -12
- package/dist/index.js +5 -5
- package/dist/install.js +2 -2
- package/dist/styles.css +5637 -5663
- package/dist/templates/adapters.d.ts +7 -1
- package/dist/templates/catalog.d.ts +5 -5
- package/dist/templates/componentBindings.d.ts +13 -0
- package/dist/templates/index.d.ts +5 -5
- package/dist/templates/index.js +2 -2
- package/dist/templates/presets.d.ts +4 -4
- package/dist/templates/types.d.ts +4 -1
- package/dist/{templates-50NPjaxL.js → templates-Cyt0Suwf.js} +322 -73
- package/dist/templates-Cyt0Suwf.js.map +1 -0
- package/dist/types/components.d.ts +6 -25
- package/dist/types/index.d.ts +1 -1
- package/dist/{useScheduleDrag-D4oWdh41.js → useExperimentData-CM6Y0u5L.js} +400 -357
- package/dist/useExperimentData-CM6Y0u5L.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionItem.test.ts +6 -6
- package/src/__tests__/components/AppLayout.test.ts +44 -0
- package/src/__tests__/components/AppSidebar.test.ts +130 -2
- package/src/__tests__/components/AppToastContainer.test.ts +0 -11
- package/src/__tests__/components/AppTopBar.test.ts +189 -120
- package/src/__tests__/components/{AppPageSelector.test.ts → AppTopBarPageSelector.test.ts} +8 -8
- package/src/__tests__/components/{AppPillNav.test.ts → AppTopBarPillNav.test.ts} +53 -6
- package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
- package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +32 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +48 -1
- package/src/__tests__/components/BioTemplateRenderer.test.ts +25 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
- package/src/__tests__/components/ComponentBindingRenderer.test.ts +278 -0
- package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
- package/src/__tests__/components/DateTimePicker.test.ts +2 -2
- package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
- package/src/__tests__/composables/experiment-utils.test.ts +30 -0
- package/src/__tests__/composables/useApi.test.ts +30 -0
- package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
- package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +7 -4
- package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +7 -7
- package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
- package/src/__tests__/composables/useControlSchema.test.ts +151 -37
- package/src/__tests__/composables/usePluginClient.test.ts +99 -2
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
- package/src/__tests__/templates/templates.test.ts +56 -0
- package/src/components/AppAvatarMenu.vue +3 -3
- package/src/components/AppLayout.story.vue +39 -0
- package/src/components/AppLayout.vue +83 -2
- package/src/components/AppPluginSwitcher.vue +5 -5
- package/src/components/AppSidebar.story.vue +113 -5
- package/src/components/AppSidebar.vue +147 -27
- package/src/components/AppTopBar.story.vue +2 -5
- package/src/components/AppTopBar.vue +35 -425
- package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
- package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
- package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
- package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
- package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
- package/src/components/BioTemplatePresetWorkspaceView.vue +12 -3
- package/src/components/BioTemplateRenderer.story.vue +2 -2
- package/src/components/BioTemplateRenderer.vue +15 -227
- package/src/components/ComponentBindingRenderer.story.vue +87 -0
- package/src/components/ComponentBindingRenderer.vue +317 -0
- package/src/components/ControlWorkspaceView.story.vue +20 -9
- package/src/components/ControlWorkspaceView.vue +43 -12
- package/src/components/DatePicker.vue +2 -2
- package/src/components/DateTimePicker.vue +2 -2
- package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
- package/src/components/DoseDesignWorkspaceView.vue +255 -0
- package/src/components/ExperimentPopover.story.vue +2 -2
- package/src/components/ExperimentPopover.vue +2 -6
- package/src/components/ExperimentSelectorModal.vue +6 -5
- package/src/components/FormBuilder.story.vue +190 -0
- package/src/components/PluginWorkspaceView.story.vue +334 -0
- package/src/components/PluginWorkspaceView.vue +708 -0
- package/src/components/SettingsModal.story.vue +87 -0
- package/src/components/WellPlate.vue +2 -2
- package/src/components/index.ts +3 -12
- package/src/components/{AppPageSelector.vue → internal/AppTopBarPageSelectorInternal.vue} +9 -9
- package/src/components/internal/AppTopBarPillNavInternal.vue +194 -0
- package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
- package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
- package/src/composables/experiment-utils.ts +26 -0
- package/src/composables/index.ts +21 -7
- package/src/composables/useApi.ts +9 -2
- package/src/composables/useAppExperiment.ts +85 -13
- package/src/composables/useBioTemplateComponents.ts +12 -0
- package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
- package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
- package/src/composables/useBioTemplateWorkspace.ts +6 -4
- package/src/composables/useControlSchema.ts +157 -69
- package/src/composables/usePluginClient.ts +50 -9
- package/src/index.ts +6 -563
- package/src/styles/components/app-layout.css +82 -0
- package/src/styles/components/app-page-selector.css +1 -1
- package/src/styles/components/app-pill-nav.css +71 -1
- package/src/styles/components/app-sidebar.css +119 -0
- package/src/styles/components/app-top-bar.css +0 -235
- package/src/styles/components/experiment-popover.css +2 -2
- package/src/styles/index.css +0 -1
- package/src/templates/adapters.ts +193 -0
- package/src/templates/catalog.ts +5 -5
- package/src/templates/componentBindings.ts +90 -3
- package/src/templates/index.ts +10 -0
- package/src/templates/packs.ts +10 -1
- package/src/templates/presets.ts +14 -4
- package/src/templates/types.ts +4 -0
- package/src/types/components.ts +6 -31
- package/src/types/index.ts +2 -6
- package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
- package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
- package/dist/components/FormSection.vue.d.ts +0 -30
- package/dist/components/GroupingModal.vue.d.ts +0 -12
- package/dist/components/SettingsButton.vue.d.ts +0 -30
- package/dist/components/ToastNotification.vue.d.ts +0 -2
- package/dist/components-D_Sr0adg.js.map +0 -1
- package/dist/composables/usePluginApi.d.ts +0 -22
- package/dist/composables-C3dpXQN5.js.map +0 -1
- package/dist/templates-50NPjaxL.js.map +0 -1
- package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
- package/src/__tests__/components/FormCompatibility.test.ts +0 -94
- package/src/__tests__/components/GroupingModal.test.ts +0 -73
- package/src/__tests__/components/SettingsButton.test.ts +0 -44
- package/src/__tests__/composables/usePluginApi.test.ts +0 -81
- package/src/components/AppPillNav.vue +0 -71
- package/src/components/FormFieldRenderer.vue +0 -35
- package/src/components/FormSection.vue +0 -37
- package/src/components/GroupingModal.story.vue +0 -52
- package/src/components/GroupingModal.vue +0 -61
- package/src/components/SettingsButton.story.vue +0 -58
- package/src/components/SettingsButton.vue +0 -64
- package/src/components/ToastNotification.vue +0 -9
- package/src/composables/usePluginApi.ts +0 -32
- package/src/styles/components/settings-button.css +0 -31
- /package/dist/__tests__/components/{AppPageSelector.test.d.ts → AppTopBarPageSelector.test.d.ts} +0 -0
- /package/dist/__tests__/components/{AppPillNav.test.d.ts → AppTopBarPillNav.test.d.ts} +0 -0
- /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
- /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
- /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
- /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
- /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
|
@@ -5,6 +5,7 @@ import axios, { type AxiosRequestConfig } from 'axios'
|
|
|
5
5
|
import {
|
|
6
6
|
buildPluginEndpointUrl,
|
|
7
7
|
createPluginClient,
|
|
8
|
+
getPluginPageSelectorItems,
|
|
8
9
|
resolvePluginBaseUrl,
|
|
9
10
|
useCurrentExperiment,
|
|
10
11
|
usePluginClient,
|
|
@@ -121,12 +122,44 @@ describe('usePluginClient', () => {
|
|
|
121
122
|
it('resolves plugin base URLs without making requests', () => {
|
|
122
123
|
expect(resolvePluginBaseUrl(contract)).toBe('/api/drp')
|
|
123
124
|
|
|
124
|
-
expect(resolvePluginBaseUrl(
|
|
125
|
+
expect(resolvePluginBaseUrl(
|
|
126
|
+
{ ...contract, plugin: { ...contract.plugin, apiPrefix: '/api/drp/' } },
|
|
127
|
+
)).toBe('/api/drp')
|
|
128
|
+
expect(resolvePluginBaseUrl(contract, '/api/explicit/')).toBe('/api/explicit')
|
|
125
129
|
|
|
126
|
-
vi.stubEnv('VITE_API_PREFIX', '/api/env')
|
|
130
|
+
vi.stubEnv('VITE_API_PREFIX', '/api/env/')
|
|
127
131
|
expect(resolvePluginBaseUrl(contract, '/api/explicit')).toBe('/api/env')
|
|
128
132
|
})
|
|
129
133
|
|
|
134
|
+
it('normalizes platform-injected plugin base URLs', () => {
|
|
135
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
136
|
+
isIntegrated: true,
|
|
137
|
+
theme: 'light',
|
|
138
|
+
plugin: {
|
|
139
|
+
id: 'drp',
|
|
140
|
+
name: 'DRP',
|
|
141
|
+
version: '1.0.0',
|
|
142
|
+
route_prefix: '/ignored/',
|
|
143
|
+
api_prefix: '/api/platform-drp/',
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
expect(resolvePluginBaseUrl(contract)).toBe('/api/platform-drp')
|
|
148
|
+
|
|
149
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
150
|
+
isIntegrated: true,
|
|
151
|
+
theme: 'light',
|
|
152
|
+
plugin: {
|
|
153
|
+
id: 'drp',
|
|
154
|
+
name: 'DRP',
|
|
155
|
+
version: '1.0.0',
|
|
156
|
+
route_prefix: '/platform-drp/',
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
expect(resolvePluginBaseUrl(contract)).toBe('/api/platform-drp')
|
|
161
|
+
})
|
|
162
|
+
|
|
130
163
|
it('builds concrete endpoint URLs without making requests', () => {
|
|
131
164
|
const url = buildPluginEndpointUrl(
|
|
132
165
|
contract,
|
|
@@ -146,6 +179,70 @@ describe('usePluginClient', () => {
|
|
|
146
179
|
expect(requestConfigs).toHaveLength(0)
|
|
147
180
|
})
|
|
148
181
|
|
|
182
|
+
it('maps plugin contract nav items to AppTopBar page selector metadata', () => {
|
|
183
|
+
const pageSelectorItems = getPluginPageSelectorItems({
|
|
184
|
+
...contract,
|
|
185
|
+
plugin: {
|
|
186
|
+
...contract.plugin,
|
|
187
|
+
name: 'Dose Designer',
|
|
188
|
+
description: 'Build dose designs',
|
|
189
|
+
icon: 'PLUGIN_ICON',
|
|
190
|
+
navItems: [
|
|
191
|
+
{ path: '/', label: 'Dashboard', icon: 'DASHBOARD_ICON', description: 'Overview' },
|
|
192
|
+
{ id: 'dose-design', path: 'dose-design', label: 'Dose Design' },
|
|
193
|
+
{ path: '/qc/review/', label: 'QC Review', icon: 'QC_ICON' },
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(pageSelectorItems).toEqual([
|
|
199
|
+
{
|
|
200
|
+
id: 'dashboard',
|
|
201
|
+
label: 'Dashboard',
|
|
202
|
+
to: '/',
|
|
203
|
+
icon: 'DASHBOARD_ICON',
|
|
204
|
+
hint: 'Overview',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'dose-design',
|
|
208
|
+
label: 'Dose Design',
|
|
209
|
+
to: '/dose-design',
|
|
210
|
+
icon: 'PLUGIN_ICON',
|
|
211
|
+
hint: 'Dose Designer',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'qc-review',
|
|
215
|
+
label: 'QC Review',
|
|
216
|
+
to: '/qc/review',
|
|
217
|
+
icon: 'QC_ICON',
|
|
218
|
+
hint: 'Dose Designer',
|
|
219
|
+
},
|
|
220
|
+
])
|
|
221
|
+
expect(requestConfigs).toHaveLength(0)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('provides a dashboard page selector fallback when contract nav items are absent', () => {
|
|
225
|
+
expect(getPluginPageSelectorItems({
|
|
226
|
+
...contract,
|
|
227
|
+
plugin: {
|
|
228
|
+
...contract.plugin,
|
|
229
|
+
name: 'Dose Designer',
|
|
230
|
+
description: 'Build dose designs',
|
|
231
|
+
icon: 'PLUGIN_ICON',
|
|
232
|
+
navItems: [],
|
|
233
|
+
},
|
|
234
|
+
})).toEqual([
|
|
235
|
+
{
|
|
236
|
+
id: 'dashboard',
|
|
237
|
+
label: 'Dose Designer',
|
|
238
|
+
to: '/',
|
|
239
|
+
icon: 'PLUGIN_ICON',
|
|
240
|
+
hint: 'Build dose designs',
|
|
241
|
+
},
|
|
242
|
+
])
|
|
243
|
+
expect(requestConfigs).toHaveLength(0)
|
|
244
|
+
})
|
|
245
|
+
|
|
149
246
|
it('can build path-only endpoint URLs with inferred experiment ids', () => {
|
|
150
247
|
window.history.replaceState({}, '', '/experiments/42/plugins/drp')
|
|
151
248
|
|
|
@@ -18,6 +18,7 @@ interface FrontendManifest {
|
|
|
18
18
|
templateExports: Array<{ name: string }>
|
|
19
19
|
templatePacks: Array<{ name: string }>
|
|
20
20
|
}
|
|
21
|
+
exports: Array<{ name: string }>
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const TEST_DIR = dirname(fileURLToPath(import.meta.url))
|
|
@@ -42,6 +43,27 @@ function parseComponentExports(): string[] {
|
|
|
42
43
|
.sort()
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function parseRootComponentExports(): string[] {
|
|
47
|
+
const content = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
|
|
48
|
+
if (/export\s+\*\s+from\s+['"]\.\/components['"]/.test(content)) {
|
|
49
|
+
return parseComponentExports()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const names = new Set<string>()
|
|
53
|
+
|
|
54
|
+
for (const match of content.matchAll(/export\s+\{([^}]*)\}\s+from\s+['"]\.\/components['"]/g)) {
|
|
55
|
+
const body = match[1].replace(/\/\/.*$/gm, '')
|
|
56
|
+
for (const item of body.split(',')) {
|
|
57
|
+
const spec = item.trim()
|
|
58
|
+
if (!spec || spec.startsWith('type ')) continue
|
|
59
|
+
const name = spec.split(/\s+as\s+/, 1)[0].trim()
|
|
60
|
+
if (/^[A-Z]\w*$/.test(name)) names.add(name)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return [...names].sort()
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
function parseDeprecatedComponentExports(): Record<string, string> {
|
|
46
68
|
const componentsDir = join(SDK_ROOT, 'src', 'components')
|
|
47
69
|
const content = readFileSync(join(componentsDir, 'index.ts'), 'utf8')
|
|
@@ -115,6 +137,27 @@ function parseDocumentedComposableExports(): string[] {
|
|
|
115
137
|
return [...new Set(names)].sort()
|
|
116
138
|
}
|
|
117
139
|
|
|
140
|
+
function parseRootComposableExports(): string[] {
|
|
141
|
+
const rootContent = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
|
|
142
|
+
const composableExports = parseDocumentedComposableExports()
|
|
143
|
+
|
|
144
|
+
if (/export\s+\*\s+from\s+['"]\.\/composables['"]/.test(rootContent)) {
|
|
145
|
+
return composableExports
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const names: string[] = []
|
|
149
|
+
for (const match of rootContent.matchAll(/export\s+\{([^}]*)\}\s+from\s+['"]\.\/composables['"]/g)) {
|
|
150
|
+
for (const item of match[1].split(',')) {
|
|
151
|
+
const spec = item.trim()
|
|
152
|
+
if (!spec || spec.startsWith('type ')) continue
|
|
153
|
+
const name = spec.split(/\s+as\s+/, 1)[0].trim()
|
|
154
|
+
if (name) names.push(name)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return [...new Set(names)].sort()
|
|
159
|
+
}
|
|
160
|
+
|
|
118
161
|
function parseTemplateExports(): string[] {
|
|
119
162
|
const indexContent = readFileSync(join(SDK_ROOT, 'src', 'templates', 'index.ts'), 'utf8')
|
|
120
163
|
const names = new Set<string>()
|
|
@@ -148,6 +191,13 @@ describe('frontend docs catalog coverage', () => {
|
|
|
148
191
|
expect(missingFromManifest(documentedComponents, publicComponents)).toEqual([])
|
|
149
192
|
})
|
|
150
193
|
|
|
194
|
+
it('re-exports every public component from the root SDK entrypoint', () => {
|
|
195
|
+
const publicComponents = parseComponentExports()
|
|
196
|
+
const rootComponents = parseRootComponentExports()
|
|
197
|
+
|
|
198
|
+
expect(missingFromManifest(publicComponents, rootComponents)).toEqual([])
|
|
199
|
+
})
|
|
200
|
+
|
|
151
201
|
it('marks every deprecated public component export with its replacement', () => {
|
|
152
202
|
const manifest = extractFrontendManifest()
|
|
153
203
|
const expected = parseDeprecatedComponentExports()
|
|
@@ -161,34 +211,28 @@ describe('frontend docs catalog coverage', () => {
|
|
|
161
211
|
expect(documented).toEqual(expected)
|
|
162
212
|
})
|
|
163
213
|
|
|
164
|
-
it('keeps compatibility
|
|
214
|
+
it('keeps retired compatibility APIs out of public SDK entrypoints', () => {
|
|
165
215
|
const rootIndex = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
|
|
216
|
+
const componentsIndex = readFileSync(join(SDK_ROOT, 'src', 'components', 'index.ts'), 'utf8')
|
|
166
217
|
const composablesIndex = readFileSync(join(SDK_ROOT, 'src', 'composables', 'index.ts'), 'utf8')
|
|
167
218
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
it('keeps deprecated usePluginApi docs focused on migration', () => {
|
|
186
|
-
const content = readFileSync(join(SDK_ROOT, 'src', 'composables', 'usePluginApi.ts'), 'utf8')
|
|
187
|
-
|
|
188
|
-
expect(content).toContain('Migration shape for generated plugins')
|
|
189
|
-
expect(content).toContain('useGeneratedPluginClient')
|
|
190
|
-
expect(content).not.toContain('const api = usePluginApi')
|
|
191
|
-
expect(content).not.toContain("fallbackPrefix: '/api/drp'")
|
|
219
|
+
const retiredNames = [
|
|
220
|
+
'ToastNotification',
|
|
221
|
+
'SettingsButton',
|
|
222
|
+
'GroupingModal',
|
|
223
|
+
'FormSection',
|
|
224
|
+
'FormFieldRenderer',
|
|
225
|
+
'AppPageSelector',
|
|
226
|
+
'AppPillNav',
|
|
227
|
+
'usePluginApi',
|
|
228
|
+
'UsePluginApiOptions',
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
for (const name of retiredNames) {
|
|
232
|
+
expect(rootIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
|
|
233
|
+
expect(componentsIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
|
|
234
|
+
expect(composablesIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
|
|
235
|
+
}
|
|
192
236
|
})
|
|
193
237
|
|
|
194
238
|
it('documents every public composable/helper export used by docs and doctor', () => {
|
|
@@ -200,6 +244,57 @@ describe('frontend docs catalog coverage', () => {
|
|
|
200
244
|
expect(missingFromManifest(documentedComposables, publicComposables)).toEqual([])
|
|
201
245
|
})
|
|
202
246
|
|
|
247
|
+
it('documents generated component binding helpers for control models', () => {
|
|
248
|
+
const manifest = extractFrontendManifest()
|
|
249
|
+
const documentedComposables = new Set(manifest.categories.composables.map(item => item.name))
|
|
250
|
+
|
|
251
|
+
expect(documentedComposables.has('defineControlComponentBindings')).toBe(true)
|
|
252
|
+
expect(documentedComposables.has('controlValuesToComponentBindings')).toBe(true)
|
|
253
|
+
expect(documentedComposables.has('controlValuesToComponentBindingsById')).toBe(true)
|
|
254
|
+
expect(documentedComposables.has('defineWellPlateDoseComponentBindings')).toBe(true)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('re-exports every documented composable/helper from the root SDK entrypoint', () => {
|
|
258
|
+
const publicComposables = parseDocumentedComposableExports()
|
|
259
|
+
const rootComposables = parseRootComposableExports()
|
|
260
|
+
|
|
261
|
+
expect(missingFromManifest(publicComposables, rootComposables)).toEqual([])
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('re-exports the public types barrel from the root SDK entrypoint', () => {
|
|
265
|
+
const rootIndex = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
|
|
266
|
+
|
|
267
|
+
expect(rootIndex).toMatch(/export\s+type\s+\*\s+from\s+['"]\.\/types['"]/)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('re-exports the public stores barrel from the root SDK entrypoint', () => {
|
|
271
|
+
const rootIndex = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
|
|
272
|
+
|
|
273
|
+
expect(rootIndex).toMatch(/export\s+\*\s+from\s+['"]\.\/stores['"]/)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('re-exports the public templates barrel from the root SDK entrypoint', () => {
|
|
277
|
+
const rootIndex = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
|
|
278
|
+
|
|
279
|
+
expect(rootIndex).toMatch(/export\s+\*\s+from\s+['"]\.\/templates['"]/)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('expands root barrel exports into the docs manifest', () => {
|
|
283
|
+
const manifest = extractFrontendManifest()
|
|
284
|
+
const rootExports = manifest.exports.map(item => item.name)
|
|
285
|
+
|
|
286
|
+
expect(rootExports).toEqual(expect.arrayContaining([
|
|
287
|
+
'BaseButton',
|
|
288
|
+
'DoseDesignWorkspaceView',
|
|
289
|
+
'WellPlate',
|
|
290
|
+
'useForm',
|
|
291
|
+
'useReagentSeries',
|
|
292
|
+
'ConversionResult',
|
|
293
|
+
'FormSectionSchema',
|
|
294
|
+
'createWellPlateScreenCollection',
|
|
295
|
+
]))
|
|
296
|
+
})
|
|
297
|
+
|
|
203
298
|
it('documents curated biology template packs', () => {
|
|
204
299
|
const manifest = extractFrontendManifest()
|
|
205
300
|
|
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
searchBioTemplatePacks,
|
|
44
44
|
searchBioTemplateCatalog,
|
|
45
45
|
searchBioTemplatePresets,
|
|
46
|
+
toBioTemplateComponentBindings,
|
|
47
|
+
toBioTemplateComponentBindingsById,
|
|
46
48
|
toBioTemplateComponentImports,
|
|
47
49
|
toBioTemplateComponentProps,
|
|
48
50
|
toBioTemplateComponentPropsByComponent,
|
|
@@ -52,6 +54,7 @@ import {
|
|
|
52
54
|
toAssayMatrixColumns,
|
|
53
55
|
toAssayMatrixDataFrame,
|
|
54
56
|
toAssayMatrixRows,
|
|
57
|
+
toAssayMatrixSampleOptions,
|
|
55
58
|
toCalibrationCurveDataFrame,
|
|
56
59
|
toCalibrationCurveRows,
|
|
57
60
|
toDoseConditions,
|
|
@@ -60,6 +63,8 @@ import {
|
|
|
60
63
|
toFlowPanelRows,
|
|
61
64
|
toInstrumentRunDataFrame,
|
|
62
65
|
toInstrumentRunRows,
|
|
66
|
+
toInstrumentRunScheduleEvents,
|
|
67
|
+
toInstrumentRunSteps,
|
|
63
68
|
toPlateMapEditorState,
|
|
64
69
|
toProtocolDataFrame,
|
|
65
70
|
toProtocolSteps,
|
|
@@ -85,6 +90,7 @@ import bioTemplateCatalogContract from '../../../../bio-template-catalog.contrac
|
|
|
85
90
|
import bioTemplatePacksContract from '../../../../bio-template-packs.contract.json'
|
|
86
91
|
import bioTemplatePresetsContract from '../../../../bio-template-presets.contract.json'
|
|
87
92
|
import type {
|
|
93
|
+
AssayMatrixTemplate,
|
|
88
94
|
DoseResponseTemplate,
|
|
89
95
|
FlowCytometryPanelTemplate,
|
|
90
96
|
InstrumentRunTemplate,
|
|
@@ -156,6 +162,9 @@ describe('bio data templates', () => {
|
|
|
156
162
|
'flow-cytometry-panel',
|
|
157
163
|
])
|
|
158
164
|
expect(getBioTemplatePackInfo('metabolomics')?.name).toBe('omics-assay')
|
|
165
|
+
expect(getBioTemplatePackInfo('metabolism')?.name).toBe('omics-assay')
|
|
166
|
+
expect(getBioTemplatePackInfo('metabolite profiling')?.name).toBe('omics-assay')
|
|
167
|
+
expect(getBioTemplatePackInfo('metabolomics')?.components).toContain('ExperimentTimeline')
|
|
159
168
|
expect(getBioTemplatePackInfo('longitudinal')?.init_command).toBe('mint init --template longitudinal-study')
|
|
160
169
|
expect(getBioTemplatePackInfo('gene expression')?.templates.at(-1)).toBe('qpcr-plate')
|
|
161
170
|
expect(getBioTemplatePackInfo('gene-expression')?.templates.at(-1)).toBe('qpcr-plate')
|
|
@@ -212,6 +221,9 @@ describe('bio data templates', () => {
|
|
|
212
221
|
'qpcr-plate',
|
|
213
222
|
])
|
|
214
223
|
expect(searchBioTemplatePresets('run queue')[0].name).toBe('lcms-batch')
|
|
224
|
+
expect(getBioTemplatePresetInfo('metabolomics')?.name).toBe('lcms-batch')
|
|
225
|
+
expect(getBioTemplatePresetInfo('metabolism')?.name).toBe('lcms-batch')
|
|
226
|
+
expect(getBioTemplatePresetInfo('metabolite profiling')?.name).toBe('lcms-batch')
|
|
215
227
|
expect(getBioTemplatePresetInfo('immunoassay')?.templates).toEqual([
|
|
216
228
|
'plate-map',
|
|
217
229
|
'sample-sheet',
|
|
@@ -286,6 +298,7 @@ describe('bio data templates', () => {
|
|
|
286
298
|
const qpcrBindings = getBioTemplateComponentBindings('gene expression')
|
|
287
299
|
const elisaBindings = getBioTemplateComponentBindings('elisa-assay')
|
|
288
300
|
const flowBindings = getBioTemplateComponentBindings('flow-cytometry-assay')
|
|
301
|
+
const lcmsBindings = getBioTemplateComponentBindings('lcms-batch')
|
|
289
302
|
const westernBindings = getBioTemplateComponentBindings('western-blot-assay')
|
|
290
303
|
const wellplateImports = toBioTemplateComponentImports('wellplate-screen')
|
|
291
304
|
|
|
@@ -296,11 +309,14 @@ describe('bio data templates', () => {
|
|
|
296
309
|
expect(qpcrBindings.map(binding => binding.component)).toContain('DataFrame')
|
|
297
310
|
expect(elisaBindings.map(binding => binding.component)).toContain('PlateMapEditor')
|
|
298
311
|
expect(elisaBindings.map(binding => binding.component)).toContain('DataFrame')
|
|
312
|
+
expect(lcmsBindings.map(binding => binding.component)).toContain('ScheduleCalendar')
|
|
313
|
+
expect(lcmsBindings.map(binding => binding.component)).toContain('ExperimentTimeline')
|
|
299
314
|
expect(flowBindings.map(binding => binding.template_id)).toEqual([
|
|
300
315
|
'sample-sheet',
|
|
301
316
|
'sample-sheet',
|
|
302
317
|
'flow-cytometry-panel',
|
|
303
318
|
'assay-matrix',
|
|
319
|
+
'assay-matrix',
|
|
304
320
|
])
|
|
305
321
|
expect(westernBindings.map(binding => binding.component)).toContain('ReagentList')
|
|
306
322
|
expect(westernBindings.map(binding => binding.component)).toContain('ExperimentTimeline')
|
|
@@ -330,14 +346,21 @@ describe('bio data templates', () => {
|
|
|
330
346
|
const state = toPlateMapEditorState(plateMapFixture as PlateMapTemplate)
|
|
331
347
|
const wells = toWellPlateWells(plateMapFixture as PlateMapTemplate)
|
|
332
348
|
const componentProps = toBioTemplateComponentProps(plateMapFixture as PlateMapTemplate)
|
|
349
|
+
const componentBindings = toBioTemplateComponentBindings(plateMapFixture as PlateMapTemplate)
|
|
333
350
|
const componentPropsById = toBioTemplateComponentPropsById(plateMapFixture as PlateMapTemplate)
|
|
351
|
+
const componentBindingsById = toBioTemplateComponentBindingsById(plateMapFixture as PlateMapTemplate)
|
|
334
352
|
const wellPlate = componentProps.find(binding => binding.component === 'WellPlate')
|
|
353
|
+
const wellPlateBinding = componentBindings.find(binding => binding.component === 'WellPlate')
|
|
335
354
|
|
|
336
355
|
expect(state.plates[0].name).toBe('Drug screen plate')
|
|
337
356
|
expect(wells.A2.sampleType).toBe('drug-a')
|
|
338
357
|
expect(wellPlate?.propsObject.format).toBe(96)
|
|
339
358
|
expect((wellPlate?.propsObject.wells as Record<string, { sampleType?: string }>).A2.sampleType).toBe('drug-a')
|
|
359
|
+
expect(wellPlateBinding?.props.format).toBe(96)
|
|
360
|
+
expect(wellPlateBinding?.propNames).toContain('wells')
|
|
340
361
|
expect(componentPropsById['plate-map:WellPlate']).toEqual(wellPlate?.propsObject)
|
|
362
|
+
expect(componentBindingsById['plate-map:WellPlate'].component).toBe('WellPlate')
|
|
363
|
+
expect(componentBindingsById['plate-map:WellPlate'].props).toEqual(wellPlate?.propsObject)
|
|
341
364
|
expect((componentPropsById['plate-map:WellPlate'].wells as Record<string, { sampleType?: string }>).A2.sampleType).toBe('drug-a')
|
|
342
365
|
})
|
|
343
366
|
|
|
@@ -347,6 +370,7 @@ describe('bio data templates', () => {
|
|
|
347
370
|
compounds: { 'Drug A': [10, 1] },
|
|
348
371
|
})
|
|
349
372
|
const propsById = toBioTemplateComponentPropsById(collection)
|
|
373
|
+
const bindingsById = toBioTemplateComponentBindingsById(collection)
|
|
350
374
|
const propsByComponent = toBioTemplateComponentPropsByComponent(collection)
|
|
351
375
|
const doseCalculatorProps = getBioTemplateComponentProps(collection, 'DoseCalculator')
|
|
352
376
|
const doseWellPlateProps = getBioTemplateComponentProps(collection, 'WellPlate', {
|
|
@@ -362,6 +386,8 @@ describe('bio data templates', () => {
|
|
|
362
386
|
'dose-response:WellPlate',
|
|
363
387
|
])
|
|
364
388
|
expect(propsById['dose-response:DoseCalculator'].mode).toBe('serial')
|
|
389
|
+
expect(bindingsById['dose-response:DoseCalculator'].props.mode).toBe('serial')
|
|
390
|
+
expect(bindingsById['dose-response:DoseCalculator'].propNames).toContain('mode')
|
|
365
391
|
expect(propsById['plate-map:WellPlate'].wells).toBeDefined()
|
|
366
392
|
expect(propsById['dose-response:WellPlate'].wells).toBeDefined()
|
|
367
393
|
expect(propsByComponent.WellPlate).toHaveLength(2)
|
|
@@ -400,6 +426,7 @@ describe('bio data templates', () => {
|
|
|
400
426
|
expect(componentPropsById['plate-map:WellPlate'].wells).toBeDefined()
|
|
401
427
|
expect(componentPropsById['calibration-curve:DataFrame'].data).toBeDefined()
|
|
402
428
|
expect(componentPropsById['assay-matrix:DataFrame'].columns).toBeDefined()
|
|
429
|
+
expect(componentPropsById['assay-matrix:SampleSelector'].samples).toEqual(['Control', 'Treatment'])
|
|
403
430
|
})
|
|
404
431
|
|
|
405
432
|
it('generates Vue snippets for concrete template component props', () => {
|
|
@@ -544,6 +571,7 @@ describe('bio data templates', () => {
|
|
|
544
571
|
expect(toInstrumentRunRows(lcmsTemplates['instrument-run'] as InstrumentRunTemplate)[0].kind).toBe('blank')
|
|
545
572
|
expect(toInstrumentRunRows(lcmsTemplates['instrument-run'] as InstrumentRunTemplate)[2].sampleId).toBe('s001')
|
|
546
573
|
expect(toTemplateDataFrame(lcmsTemplates['assay-matrix']).columns.map(column => column.key)).toContain('glucose')
|
|
574
|
+
expect(toAssayMatrixSampleOptions(lcmsTemplates['assay-matrix'] as AssayMatrixTemplate).map(option => option.label)).toEqual(['S001', 'S002'])
|
|
547
575
|
expect(Object.keys(flowTemplates)).toEqual(['sample-sheet', 'flow-cytometry-panel', 'assay-matrix'])
|
|
548
576
|
expect(flow.metadata?.preset).toBe('flow-cytometry-assay')
|
|
549
577
|
expect(flowTemplates['flow-cytometry-panel'].data).toMatchObject({
|
|
@@ -909,6 +937,10 @@ describe('bio data templates', () => {
|
|
|
909
937
|
const assayFrame = toAssayMatrixDataFrame(template)
|
|
910
938
|
expect(assayFrame.rowKey).toBe('sampleId')
|
|
911
939
|
expect(assayFrame.data[0].lactate).toBe(1.2)
|
|
940
|
+
expect(toAssayMatrixSampleOptions(template)).toEqual([
|
|
941
|
+
{ value: 's001', label: 'S001', description: undefined },
|
|
942
|
+
{ value: 's002', label: 'S002', description: undefined },
|
|
943
|
+
])
|
|
912
944
|
expect(toTemplateDataFrame(template).columns.map(column => column.key)).toContain('glucose')
|
|
913
945
|
})
|
|
914
946
|
|
|
@@ -966,6 +998,11 @@ describe('bio data templates', () => {
|
|
|
966
998
|
})
|
|
967
999
|
const rows = toInstrumentRunRows(template)
|
|
968
1000
|
const frame = toInstrumentRunDataFrame(template)
|
|
1001
|
+
const steps = toInstrumentRunSteps(template)
|
|
1002
|
+
const events = toInstrumentRunScheduleEvents(template)
|
|
1003
|
+
const componentProps = toBioTemplateComponentProps(template)
|
|
1004
|
+
const calendar = componentProps.find(binding => binding.component === 'ScheduleCalendar')
|
|
1005
|
+
const timeline = componentProps.find(binding => binding.component === 'ExperimentTimeline')
|
|
969
1006
|
|
|
970
1007
|
expect(template.template_id).toBe('instrument-run')
|
|
971
1008
|
expect(template.data.items.map(item => item.kind)).toEqual(['blank', 'qc', 'sample', 'sample', 'qc'])
|
|
@@ -975,6 +1012,25 @@ describe('bio data templates', () => {
|
|
|
975
1012
|
expect(frame.rowKey).toBe('id')
|
|
976
1013
|
expect(frame.columns.map(column => column.key)).toContain('status')
|
|
977
1014
|
expect(toTemplateDataFrame(template).data[3].sampleId).toBe('s002')
|
|
1015
|
+
expect(steps[2]).toMatchObject({
|
|
1016
|
+
id: 's001-run',
|
|
1017
|
+
type: 'measurement',
|
|
1018
|
+
name: 'S001',
|
|
1019
|
+
status: 'pending',
|
|
1020
|
+
order: 3,
|
|
1021
|
+
})
|
|
1022
|
+
expect(steps[2].parameters?.method).toBe('Default method')
|
|
1023
|
+
expect(events[0]).toMatchObject({
|
|
1024
|
+
id: 'blank-start',
|
|
1025
|
+
title: 'Blank start',
|
|
1026
|
+
start: '2024-01-01T08:00:00.000Z',
|
|
1027
|
+
end: '2024-01-01T08:10:00.000Z',
|
|
1028
|
+
status: 'pending',
|
|
1029
|
+
})
|
|
1030
|
+
expect(events[2].title).toBe('S001')
|
|
1031
|
+
expect(calendar?.propsObject.modelValue).toBe(events[0].start)
|
|
1032
|
+
expect(calendar?.propsObject.events).toEqual(events)
|
|
1033
|
+
expect(timeline?.propsObject.modelValue).toEqual(steps)
|
|
978
1034
|
})
|
|
979
1035
|
|
|
980
1036
|
it('creates qPCR plate templates and adapts reactions to dataframe and wells', () => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { computed } from 'vue'
|
|
4
4
|
import { useDropdownState } from '../composables/useDropdownState'
|
|
5
5
|
import type { AccountMenuItem } from '../types/components'
|
|
6
|
-
import
|
|
6
|
+
import ActionItemInternal from './internal/ActionItemInternal.vue'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
userName?: string
|
|
@@ -86,7 +86,7 @@ function handleSignOut() {
|
|
|
86
86
|
<slot name="items" :close="closeSilently">
|
|
87
87
|
<template v-for="item in items" :key="item.id">
|
|
88
88
|
<div v-if="item.divider" class="mint-avatar-menu__divider" role="separator" />
|
|
89
|
-
<
|
|
89
|
+
<ActionItemInternal
|
|
90
90
|
v-else
|
|
91
91
|
:href="item.href"
|
|
92
92
|
:to="item.to"
|
|
@@ -99,7 +99,7 @@ function handleSignOut() {
|
|
|
99
99
|
</span>
|
|
100
100
|
<span class="mint-avatar-menu__item-label">{{ item.label }}</span>
|
|
101
101
|
<span v-if="item.rightLabel" class="mint-avatar-menu__item-right">{{ item.rightLabel }}</span>
|
|
102
|
-
</
|
|
102
|
+
</ActionItemInternal>
|
|
103
103
|
</template>
|
|
104
104
|
</slot>
|
|
105
105
|
|
|
@@ -48,6 +48,7 @@ const logScale = ref(false)
|
|
|
48
48
|
:sidebar-position="state.sidebarPosition"
|
|
49
49
|
:floating="state.floating ?? true"
|
|
50
50
|
:sidebar-width="state.sidebarWidth"
|
|
51
|
+
responsive-sidebar
|
|
51
52
|
>
|
|
52
53
|
<template #topbar>
|
|
53
54
|
<AppTopBar
|
|
@@ -216,6 +217,44 @@ const logScale = ref(false)
|
|
|
216
217
|
</div>
|
|
217
218
|
</Variant>
|
|
218
219
|
|
|
220
|
+
<Variant title="Responsive Sidebar">
|
|
221
|
+
<div style="height: 500px; max-width: 760px; border: 1px solid var(--border-color, #e2e8f0);">
|
|
222
|
+
<AppLayout
|
|
223
|
+
floating
|
|
224
|
+
responsive-sidebar
|
|
225
|
+
sidebar-width="320px"
|
|
226
|
+
>
|
|
227
|
+
<template #topbar>
|
|
228
|
+
<AppTopBar title="Responsive Analysis" home-path="" />
|
|
229
|
+
</template>
|
|
230
|
+
<template #sidebar>
|
|
231
|
+
<AppSidebar
|
|
232
|
+
variant="analysis"
|
|
233
|
+
title="Feature Filters"
|
|
234
|
+
subtitle="12 active samples"
|
|
235
|
+
:badge="2"
|
|
236
|
+
:panels="toolPanels"
|
|
237
|
+
active-view="analysis"
|
|
238
|
+
>
|
|
239
|
+
<template #section-parameters>
|
|
240
|
+
<BaseSlider v-model="threshold" label="Threshold" :min="0" :max="100" />
|
|
241
|
+
<BaseSelect v-model="method" label="Method" :options="methods" />
|
|
242
|
+
</template>
|
|
243
|
+
<template #section-filters>
|
|
244
|
+
<BaseToggle v-model="showOutliers" label="Exclude outliers" />
|
|
245
|
+
</template>
|
|
246
|
+
</AppSidebar>
|
|
247
|
+
</template>
|
|
248
|
+
<div style="padding: 2rem;">
|
|
249
|
+
<p style="color: var(--text-muted, #94a3b8);">
|
|
250
|
+
At narrow widths, AppLayout owns the sidebar toggle and backdrop,
|
|
251
|
+
while AppSidebar owns the panel chrome and collapse state.
|
|
252
|
+
</p>
|
|
253
|
+
</div>
|
|
254
|
+
</AppLayout>
|
|
255
|
+
</div>
|
|
256
|
+
</Variant>
|
|
257
|
+
|
|
219
258
|
<Variant title="With Experiment Selector">
|
|
220
259
|
<ExperimentProvider>
|
|
221
260
|
<div style="height: 600px;">
|