@morscherlab/mint-sdk 1.4.0-beta.16 → 1.4.0-beta.18
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 +14 -3
- package/dist/{AlertBox-SnsbVow-.js → AlertBox-CzLg0UaL.js} +2 -2
- package/dist/{AlertBox-SnsbVow-.js.map → AlertBox-CzLg0UaL.js.map} +1 -1
- package/dist/{AppSidebar-CB0-RQ4m.js → AppSidebar-6oTT-gPO.js} +43 -13
- package/dist/AppSidebar-6oTT-gPO.js.map +1 -0
- package/dist/{ExperimentPopover-ChKCrklH.js → ExperimentPopover-9l4QZlvQ.js} +2 -2
- package/dist/{ExperimentPopover-ChKCrklH.js.map → ExperimentPopover-9l4QZlvQ.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-BqifjNaN.js → ExperimentSelectorModal-pR5fK3pA.js} +3 -3
- package/dist/{ExperimentSelectorModal-BqifjNaN.js.map → ExperimentSelectorModal-pR5fK3pA.js.map} +1 -1
- package/dist/{SegmentedControl-hOgBVdju.js → SegmentedControl-CJ7EAmOq.js} +2 -2
- package/dist/{SegmentedControl-hOgBVdju.js.map → SegmentedControl-CJ7EAmOq.js.map} +1 -1
- package/dist/{SettingsModal-CoZK3EDN.js → SettingsModal-Dyitgzfp.js} +6 -6
- package/dist/{SettingsModal-CoZK3EDN.js.map → SettingsModal-Dyitgzfp.js.map} +1 -1
- package/dist/components/AppTopBar.vue.d.ts +7 -0
- package/dist/components/JobsStatusTray.vue.d.ts +11 -4
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
- package/dist/composables/useJobCenter.d.ts +11 -3
- package/dist/{controlSchemaModel-CIH5Hqpw.js → controlSchemaModel-DCUOx8ll.js} +2 -2
- package/dist/{controlSchemaModel-CIH5Hqpw.js.map → controlSchemaModel-DCUOx8ll.js.map} +1 -1
- package/dist/{data-OZ94DhVC.js → data-BIiV4kKw.js} +9 -9
- package/dist/{data-OZ94DhVC.js.map → data-BIiV4kKw.js.map} +1 -1
- package/dist/data.js +7 -7
- package/dist/index.js +16 -16
- package/dist/{install-BKgb5qZw.js → install-Dg-Z6ze5.js} +13 -13
- package/dist/{install-BKgb5qZw.js.map → install-Dg-Z6ze5.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-ZDKlx1Oo.js → lab-BHVT1qN2.js} +9 -9
- package/dist/{lab-ZDKlx1Oo.js.map → lab-BHVT1qN2.js.map} +1 -1
- package/dist/lab.js +4 -4
- package/dist/{records-B4yl2oIu.js → records-CR_pYzIP.js} +62 -62
- package/dist/{records-B4yl2oIu.js.map → records-CR_pYzIP.js.map} +1 -1
- package/dist/{runtime-Cz1GJa_O.js → runtime-cHv1phcc.js} +379 -31
- package/dist/runtime-cHv1phcc.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +6 -6
- package/dist/{shell-kq9p4VsX.js → shell-BDuZf2zb.js} +4 -4
- package/dist/{shell-kq9p4VsX.js.map → shell-BDuZf2zb.js.map} +1 -1
- package/dist/shell.js +4 -4
- package/dist/styles.css +163 -307
- package/dist/types/components.d.ts +2 -0
- package/dist/{useExperimentSamples-BLx29CkM.js → useExperimentSamples-Dno1fFMq.js} +3 -3
- package/dist/{useExperimentSamples-BLx29CkM.js.map → useExperimentSamples-Dno1fFMq.js.map} +1 -1
- package/dist/{useListReorder-C_8RYeVP.js → useListReorder-CQ9OCScc.js} +76 -76
- package/dist/useListReorder-CQ9OCScc.js.map +1 -0
- package/dist/{useRackEditor-BhYlXKFM.js → useRackEditor-wL3kPrRT.js} +6 -6
- package/dist/{useRackEditor-BhYlXKFM.js.map → useRackEditor-wL3kPrRT.js.map} +1 -1
- package/dist/useRealtime-BW02xy4Z.js +398 -0
- package/dist/useRealtime-BW02xy4Z.js.map +1 -0
- package/dist/{useRequest-C9uwM79q.js → useRequest-CDkrXNU7.js} +2 -2
- package/dist/{useRequest-C9uwM79q.js.map → useRequest-CDkrXNU7.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.test.ts +33 -2
- package/src/__tests__/components/GeneratedPluginView.test.ts +1 -1
- package/src/__tests__/components/JobsStatusTray.test.ts +137 -71
- package/src/__tests__/components/PluginWorkspaceView.test.ts +56 -1
- package/src/__tests__/composables/useJobCenter.test.ts +223 -3
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +3 -3
- package/src/__tests__/styles/v2AuditRules.test.ts +8 -0
- package/src/components/AppTopBar.vue +14 -0
- package/src/components/GeneratedPluginView.vue +4 -9
- package/src/components/JobsStatusTray.story.vue +78 -43
- package/src/components/JobsStatusTray.vue +171 -279
- package/src/components/PluginWorkspaceView.props.ts +3 -0
- package/src/components/PluginWorkspaceView.vue +17 -0
- package/src/components/internal/WorkspaceShellInternal.vue +12 -0
- package/src/composables/useJobCenter.ts +127 -6
- package/src/runtime.ts +0 -1
- package/src/styles/components/app-top-bar.css +7 -0
- package/src/styles/components/jobs-status-tray.css +129 -296
- package/src/styles/variables.css +16 -0
- package/src/types/components.ts +2 -0
- package/dist/AppSidebar-CB0-RQ4m.js.map +0 -1
- package/dist/runtime-Cz1GJa_O.js.map +0 -1
- package/dist/useListReorder-C_8RYeVP.js.map +0 -1
- package/dist/useRealtime-CUMbW_ln.js +0 -699
- package/dist/useRealtime-CUMbW_ln.js.map +0 -1
|
@@ -5,20 +5,25 @@ import {
|
|
|
5
5
|
ref,
|
|
6
6
|
shallowRef,
|
|
7
7
|
toValue,
|
|
8
|
+
useId,
|
|
9
|
+
watch,
|
|
8
10
|
type ComputedRef,
|
|
9
11
|
type MaybeRefOrGetter,
|
|
10
12
|
type Ref,
|
|
11
13
|
type ShallowRef,
|
|
12
14
|
} from 'vue'
|
|
13
15
|
|
|
14
|
-
import { isActiveJobStatus, normalizeJobState } from '../jobs'
|
|
16
|
+
import { isActiveJobStatus, jobStatusLabel, normalizeJobPercent, normalizeJobState } from '../jobs'
|
|
15
17
|
import type {
|
|
16
18
|
JobListPayload,
|
|
17
19
|
JobState,
|
|
18
20
|
JobStateInput,
|
|
21
|
+
TopBarJob,
|
|
19
22
|
} from '../types/jobs'
|
|
23
|
+
import { usePlatform } from './usePlatform'
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
/** How long a job that just completed stays in the top-bar run slot. */
|
|
26
|
+
const FINISHED_HOLD_MS = 6000
|
|
22
27
|
|
|
23
28
|
/**
|
|
24
29
|
* The canonical owner of a collection of plugin jobs.
|
|
@@ -143,6 +148,8 @@ function duplicatePluginJobId(jobId: string): Error {
|
|
|
143
148
|
export interface UseJobCenterOptions {
|
|
144
149
|
source?: MaybeRefOrGetter<PluginJobCenterSource | undefined>
|
|
145
150
|
loadOnMount?: boolean
|
|
151
|
+
/** Maps the chosen own active job to the top-bar chip; `null` keeps the default mapping. */
|
|
152
|
+
topBarJob?: (job: JobState) => TopBarJob | null
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
export interface UseJobCenterReturn {
|
|
@@ -155,14 +162,21 @@ export interface UseJobCenterReturn {
|
|
|
155
162
|
canCancel: ComputedRef<boolean>
|
|
156
163
|
canDelete: ComputedRef<boolean>
|
|
157
164
|
canClearFinished: ComputedRef<boolean>
|
|
165
|
+
/** DOM id of the JobsStatusTray panel, for `AppTopBar`'s `jobsPanelId` prop. */
|
|
166
|
+
panelId: string
|
|
167
|
+
/** The viewer's own job for `AppTopBar`'s `job` prop. */
|
|
168
|
+
topBarJob: ComputedRef<TopBarJob | null>
|
|
169
|
+
/** Jobs in the list with status `completed`, for `AppTopBar`'s `jobsDone` prop. */
|
|
170
|
+
completedCount: ComputedRef<number>
|
|
158
171
|
isOpen: Ref<boolean>
|
|
159
|
-
activeTab: Ref<JobsStatusTrayTab>
|
|
160
172
|
isLoading: Ref<boolean>
|
|
161
173
|
error: ShallowRef<string | null>
|
|
162
174
|
pendingJobIds: Ref<Set<string>>
|
|
163
175
|
open: () => void
|
|
164
176
|
close: () => void
|
|
165
177
|
toggle: () => void
|
|
178
|
+
/** The top-bar chip: with the tray closed and none of the viewer's jobs active, selects the held just-completed job; else toggles. */
|
|
179
|
+
handleTopBarClick: (onSelect: (job: JobState) => void) => void
|
|
166
180
|
refresh: () => Promise<boolean>
|
|
167
181
|
cancelJob: (job: JobState) => Promise<boolean>
|
|
168
182
|
deleteJob: (job: JobState) => Promise<boolean>
|
|
@@ -170,6 +184,24 @@ export interface UseJobCenterReturn {
|
|
|
170
184
|
isPending: (job: JobState) => boolean
|
|
171
185
|
}
|
|
172
186
|
|
|
187
|
+
/** The stored seen failures, or null when nothing is stored or storage is blocked. */
|
|
188
|
+
function readSeenFailed(key: string): Set<string> | null {
|
|
189
|
+
try {
|
|
190
|
+
const parsed: unknown = JSON.parse(globalThis.localStorage?.getItem(key) ?? 'null')
|
|
191
|
+
return Array.isArray(parsed) ? new Set(parsed.filter((id): id is string => typeof id === 'string')) : null
|
|
192
|
+
} catch {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function writeSeenFailed(key: string, seen: Set<string>): void {
|
|
198
|
+
try {
|
|
199
|
+
globalThis.localStorage?.setItem(key, JSON.stringify([...seen]))
|
|
200
|
+
} catch {
|
|
201
|
+
// Private windows and full storage: the state lasts until reload.
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
173
205
|
function errorMessage(error: unknown, fallback: string): string {
|
|
174
206
|
return error instanceof Error ? error.message : fallback
|
|
175
207
|
}
|
|
@@ -184,7 +216,6 @@ export function useJobCenter(
|
|
|
184
216
|
options: UseJobCenterOptions = {},
|
|
185
217
|
): UseJobCenterReturn {
|
|
186
218
|
const isOpen = ref(false)
|
|
187
|
-
const activeTab = ref<JobsStatusTrayTab>('active')
|
|
188
219
|
const isLoading = ref(false)
|
|
189
220
|
const error = shallowRef<string | null>(null)
|
|
190
221
|
const pendingJobIds = ref(new Set<string>())
|
|
@@ -200,6 +231,88 @@ export function useJobCenter(
|
|
|
200
231
|
const historyJobs = computed(() => jobs.value.filter(job => !isActiveJobStatus(job.status)))
|
|
201
232
|
const deletableJobs = computed(() => historyJobs.value.filter(job => job.can_delete))
|
|
202
233
|
const featuredJob = computed(() => activeJobs.value[0] ?? historyJobs.value[0] ?? null)
|
|
234
|
+
const completedCount = computed(() => jobs.value.filter(job => job.status === 'completed').length)
|
|
235
|
+
|
|
236
|
+
// Top-bar run slot: the viewer's own jobs only. Standalone or with auth off
|
|
237
|
+
// (no signed-in user), every job is the viewer's.
|
|
238
|
+
const { integrated, user, plugin, isAdmin } = usePlatform()
|
|
239
|
+
const isOwnJob = (job: JobState) => {
|
|
240
|
+
if (!integrated.value) return true
|
|
241
|
+
// With no user, isAdmin is true only with auth off.
|
|
242
|
+
if (user.value == null) return isAdmin.value
|
|
243
|
+
return (job.owner_user_id ?? job.user_id) === String(user.value.id)
|
|
244
|
+
}
|
|
245
|
+
const ownActiveJobs = computed(() => activeJobs.value.filter(isOwnJob))
|
|
246
|
+
const justFinished = shallowRef<JobState | null>(null)
|
|
247
|
+
// The live held job; it drops out once the job leaves the list.
|
|
248
|
+
const heldJob = computed(() => {
|
|
249
|
+
const held = justFinished.value
|
|
250
|
+
return held ? jobs.value.find(job => job.job_id === held.job_id) ?? null : null
|
|
251
|
+
})
|
|
252
|
+
let finishedTimer: ReturnType<typeof setTimeout> | undefined
|
|
253
|
+
// Compare active jobs within one source only: a replaced source starts over.
|
|
254
|
+
watch([source, ownActiveJobs], ([currentSource, now], [previousSource, before]) => {
|
|
255
|
+
if (currentSource !== previousSource) {
|
|
256
|
+
justFinished.value = null
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
const nowIds = new Set(now.map(job => job.job_id))
|
|
260
|
+
const left = new Set(before.filter(job => !nowIds.has(job.job_id)).map(job => job.job_id))
|
|
261
|
+
const finished = jobs.value.find(job => left.has(job.job_id) && job.status === 'completed')
|
|
262
|
+
if (!finished) return
|
|
263
|
+
justFinished.value = finished
|
|
264
|
+
clearTimeout(finishedTimer)
|
|
265
|
+
finishedTimer = setTimeout(() => { justFinished.value = null }, FINISHED_HOLD_MS)
|
|
266
|
+
})
|
|
267
|
+
// ponytail: the seen set only grows; prune to listed jobs if the stored array ever matters.
|
|
268
|
+
const seenFailedKey = `mint.jobs.${plugin.value?.id ?? 'standalone'}.seen-failed`
|
|
269
|
+
const storedSeenFailed = readSeenFailed(seenFailedKey)
|
|
270
|
+
const seenFailed = shallowRef(storedSeenFailed ?? new Set<string>())
|
|
271
|
+
const unseenFailedJobs = computed(() => historyJobs.value.filter(job => (
|
|
272
|
+
job.status === 'failed' && isOwnJob(job) && !seenFailed.value.has(job.job_id)
|
|
273
|
+
)))
|
|
274
|
+
function markFailedSeen(): void {
|
|
275
|
+
seenFailed.value = new Set([...seenFailed.value, ...unseenFailedJobs.value.map(job => job.job_id)])
|
|
276
|
+
writeSeenFailed(seenFailedKey, seenFailed.value)
|
|
277
|
+
}
|
|
278
|
+
// Nothing stored (new browser, cleared or blocked storage): the failures at the
|
|
279
|
+
// initial load are the baseline, so only later failures alert. Without a load
|
|
280
|
+
// step, the first non-empty list stands in for it.
|
|
281
|
+
let hasBaseline = storedSeenFailed !== null
|
|
282
|
+
let awaitingLoad = options.loadOnMount !== false && Boolean(source.value?.refresh)
|
|
283
|
+
function takeBaseline(): void {
|
|
284
|
+
if (hasBaseline || !active) return
|
|
285
|
+
hasBaseline = true
|
|
286
|
+
markFailedSeen()
|
|
287
|
+
}
|
|
288
|
+
watch(jobs, (list) => {
|
|
289
|
+
if (!awaitingLoad && list.length > 0) takeBaseline()
|
|
290
|
+
}, { immediate: true })
|
|
291
|
+
// The open tray shows every failure, including one that happens while it is open.
|
|
292
|
+
watch([isOpen, unseenFailedJobs], ([open, unseen]) => {
|
|
293
|
+
if (open && unseen.length > 0) markFailedSeen()
|
|
294
|
+
})
|
|
295
|
+
const topBarJob = computed<TopBarJob | null>(() => {
|
|
296
|
+
const own = ownActiveJobs.value
|
|
297
|
+
const shown = own.find(job => job.status === 'awaiting_input') ?? own[0]
|
|
298
|
+
if (shown) {
|
|
299
|
+
return options.topBarJob?.(shown) ?? {
|
|
300
|
+
status: shown.status,
|
|
301
|
+
label: shown.progress.message || `${shown.name} ${jobStatusLabel(shown.status).toLowerCase()}`,
|
|
302
|
+
percent: shown.status === 'running' ? normalizeJobPercent(shown.progress.percent) : null,
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (heldJob.value) return { status: 'completed', label: `${heldJob.value.name} completed` }
|
|
306
|
+
const failed = unseenFailedJobs.value.length
|
|
307
|
+
return failed > 0 ? { status: 'failed', label: `${failed} failed` } : null
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
function handleTopBarClick(onSelect: (job: JobState) => void): void {
|
|
311
|
+
const finished = heldJob.value
|
|
312
|
+
if (!isOpen.value && ownActiveJobs.value.length === 0 && finished) onSelect(finished)
|
|
313
|
+
else isOpen.value = !isOpen.value
|
|
314
|
+
}
|
|
315
|
+
|
|
203
316
|
const canRefresh = computed(() => Boolean(source.value?.refresh))
|
|
204
317
|
const canCancel = computed(() => Boolean(source.value?.cancel))
|
|
205
318
|
const canDelete = computed(() => Boolean(source.value?.delete))
|
|
@@ -290,11 +403,16 @@ export function useJobCenter(
|
|
|
290
403
|
}
|
|
291
404
|
|
|
292
405
|
onMounted(() => {
|
|
293
|
-
if (options.loadOnMount
|
|
406
|
+
if (options.loadOnMount === false) return
|
|
407
|
+
void refresh().then((loaded) => {
|
|
408
|
+
awaitingLoad = false
|
|
409
|
+
if (loaded || jobs.value.length > 0) takeBaseline()
|
|
410
|
+
})
|
|
294
411
|
})
|
|
295
412
|
onUnmounted(() => {
|
|
296
413
|
active = false
|
|
297
414
|
requestGeneration += 1
|
|
415
|
+
clearTimeout(finishedTimer)
|
|
298
416
|
})
|
|
299
417
|
|
|
300
418
|
return {
|
|
@@ -307,14 +425,17 @@ export function useJobCenter(
|
|
|
307
425
|
canCancel,
|
|
308
426
|
canDelete,
|
|
309
427
|
canClearFinished,
|
|
428
|
+
panelId: `mint-jobs-tray-${useId()}`,
|
|
429
|
+
topBarJob,
|
|
430
|
+
completedCount,
|
|
310
431
|
isOpen,
|
|
311
|
-
activeTab,
|
|
312
432
|
isLoading,
|
|
313
433
|
error,
|
|
314
434
|
pendingJobIds,
|
|
315
435
|
open: () => { isOpen.value = true },
|
|
316
436
|
close: () => { isOpen.value = false },
|
|
317
437
|
toggle: () => { isOpen.value = !isOpen.value },
|
|
438
|
+
handleTopBarClick,
|
|
318
439
|
refresh,
|
|
319
440
|
cancelJob,
|
|
320
441
|
deleteJob,
|
package/src/runtime.ts
CHANGED
|
@@ -338,6 +338,13 @@
|
|
|
338
338
|
flex-shrink: 0;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
.mint-topbar__job-count {
|
|
342
|
+
color: var(--text-muted);
|
|
343
|
+
font-family: var(--font-mono);
|
|
344
|
+
font-size: 0.6875rem;
|
|
345
|
+
font-variant-numeric: tabular-nums;
|
|
346
|
+
}
|
|
347
|
+
|
|
341
348
|
.mint-topbar__job-icon--spin {
|
|
342
349
|
animation: mint-topbar-job-spin 1s linear infinite;
|
|
343
350
|
}
|