@morscherlab/mint-sdk 1.2.0-beta.9 → 1.2.0

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 (152) hide show
  1. package/README.md +30 -0
  2. package/dist/{ExperimentPopover-oL0fg_A0.js → ExperimentPopover-CJdNlCZb.js} +21 -14
  3. package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-BeEOomw_.js → ExperimentSelectorModal-DCXmwKNS.js} +5 -5
  5. package/dist/{ExperimentSelectorModal-BeEOomw_.js.map → ExperimentSelectorModal-DCXmwKNS.js.map} +1 -1
  6. package/dist/{SettingsModal-Dphh3RrI.js → SettingsModal-BYR20I4c.js} +5 -5
  7. package/dist/{SettingsModal-Dphh3RrI.js.map → SettingsModal-BYR20I4c.js.map} +1 -1
  8. package/dist/{Skeleton-DxtwJ5gt.js → Skeleton-BAF3CKY7.js} +3 -3
  9. package/dist/{Skeleton-DxtwJ5gt.js.map → Skeleton-BAF3CKY7.js.map} +1 -1
  10. package/dist/__tests__/apiErrors.test.d.ts +1 -0
  11. package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
  12. package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
  13. package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
  14. package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
  15. package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
  16. package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
  17. package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
  18. package/dist/apiErrors.d.ts +26 -0
  19. package/dist/components/BaseInput.vue.d.ts +1 -1
  20. package/dist/components/BaseSelect.vue.d.ts +1 -1
  21. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  22. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  23. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
  24. package/dist/components/ConcentrationInput.vue.d.ts +1 -1
  25. package/dist/components/DatePicker.vue.d.ts +1 -1
  26. package/dist/components/DateTimePicker.vue.d.ts +1 -1
  27. package/dist/components/FileBrowserModal.vue.d.ts +99 -0
  28. package/dist/components/FilePicker.vue.d.ts +32 -0
  29. package/dist/components/FileUploader.vue.d.ts +5 -2
  30. package/dist/components/FormulaInput.vue.d.ts +1 -1
  31. package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
  32. package/dist/components/MobileSupportGate.vue.d.ts +1 -1
  33. package/dist/components/MoleculeInput.vue.d.ts +1 -1
  34. package/dist/components/NumberInput.vue.d.ts +1 -1
  35. package/dist/components/PlotlyChart.vue.d.ts +59 -0
  36. package/dist/components/ResourceCard.vue.d.ts +1 -1
  37. package/dist/components/SecretInput.vue.d.ts +1 -1
  38. package/dist/components/SequenceInput.vue.d.ts +1 -1
  39. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  40. package/dist/components/TagsInput.vue.d.ts +1 -1
  41. package/dist/components/TimePicker.vue.d.ts +1 -1
  42. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  43. package/dist/components/UnitInput.vue.d.ts +1 -1
  44. package/dist/components/WellPlate.vue.d.ts +1 -1
  45. package/dist/components/fileBrowserPresentation.d.ts +10 -0
  46. package/dist/components/index.d.ts +4 -0
  47. package/dist/components/index.js +8 -8
  48. package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
  49. package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
  50. package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
  51. package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
  52. package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
  53. package/dist/{components-CgAUzF8F.js → components-DIum5hkx.js} +4286 -1866
  54. package/dist/components-DIum5hkx.js.map +1 -0
  55. package/dist/composables/filePicker/localFiles.d.ts +3 -0
  56. package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
  57. package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
  58. package/dist/composables/filePicker/validation.d.ts +5 -0
  59. package/dist/composables/index.d.ts +2 -0
  60. package/dist/composables/index.js +10 -10
  61. package/dist/composables/useApi.d.ts +14 -7
  62. package/dist/composables/useFileBrowser.d.ts +55 -0
  63. package/dist/composables/useFilePicker.d.ts +189 -0
  64. package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
  65. package/dist/{composables-B_pia3hO.js → composables-mFXsTA4g.js} +300 -11
  66. package/dist/composables-mFXsTA4g.js.map +1 -0
  67. package/dist/{errors-_W0-LbA6.js → errors-BLuqMee5.js} +78 -15
  68. package/dist/errors-BLuqMee5.js.map +1 -0
  69. package/dist/{experiment-Cwl9AYxw.js → experiment-CVDRAnqp.js} +2 -2
  70. package/dist/{experiment-Cwl9AYxw.js.map → experiment-CVDRAnqp.js.map} +1 -1
  71. package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
  72. package/dist/{experiment-utils-DV3DrvE_.js.map → experiment-utils-CV2Wza3z.js.map} +1 -1
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.js +15 -15
  75. package/dist/install.js +2 -2
  76. package/dist/stores/index.js +2 -2
  77. package/dist/styles.css +2369 -1377
  78. package/dist/templates/index.js +2 -2
  79. package/dist/{templates-Qq113pyZ.js → templates-CmGfmBRA.js} +2 -2
  80. package/dist/{templates-Qq113pyZ.js.map → templates-CmGfmBRA.js.map} +1 -1
  81. package/dist/types/fileBrowserTypes.d.ts +77 -0
  82. package/dist/types/filePicker.d.ts +77 -0
  83. package/dist/types/index.d.ts +2 -0
  84. package/dist/{useControlSchema-aMZlb4Sc.js → useControlSchema-DldpxNOP.js} +42 -37
  85. package/dist/useControlSchema-DldpxNOP.js.map +1 -0
  86. package/dist/{useDropdownState-DitCtrTT.js → useDropdownState-C7hRiVQf.js} +2 -2
  87. package/dist/{useDropdownState-DitCtrTT.js.map → useDropdownState-C7hRiVQf.js.map} +1 -1
  88. package/dist/{useExperimentSelector-I8HC8hUu.js → useExperimentSelector-CEcIaueJ.js} +4 -4
  89. package/dist/{useExperimentSelector-I8HC8hUu.js.map → useExperimentSelector-CEcIaueJ.js.map} +1 -1
  90. package/dist/{useFormBuilder-TCxNnqeb.js → useFormBuilder-z5L_dUIy.js} +2 -2
  91. package/dist/{useFormBuilder-TCxNnqeb.js.map → useFormBuilder-z5L_dUIy.js.map} +1 -1
  92. package/dist/{useProtocolTemplates-BmX_iVBR.js → validation-uwoJGEb6.js} +53 -8
  93. package/dist/validation-uwoJGEb6.js.map +1 -0
  94. package/package.json +1 -1
  95. package/src/__tests__/apiErrors.test.ts +81 -0
  96. package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
  97. package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
  98. package/src/__tests__/components/FilePicker.state.test.ts +287 -0
  99. package/src/__tests__/components/FilePicker.test.ts +487 -0
  100. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
  101. package/src/__tests__/components/PlotlyChart.test.ts +305 -0
  102. package/src/__tests__/components/SegmentedControl.test.ts +19 -0
  103. package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
  104. package/src/__tests__/composables/useApi.test.ts +92 -9
  105. package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
  106. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
  107. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
  108. package/src/__tests__/composables/usePluginClient.test.ts +3 -0
  109. package/src/apiErrors.ts +85 -0
  110. package/src/components/ExperimentPopover.vue +10 -2
  111. package/src/components/FileBrowserModal.story.vue +203 -0
  112. package/src/components/FileBrowserModal.vue +384 -0
  113. package/src/components/FilePicker.story.vue +44 -0
  114. package/src/components/FilePicker.vue +284 -0
  115. package/src/components/FileUploader.vue +32 -14
  116. package/src/components/GeneratedPlotlyResult.vue +34 -69
  117. package/src/components/GeneratedResultRenderer.vue +0 -5
  118. package/src/components/PlotlyChart.story.vue +61 -0
  119. package/src/components/PlotlyChart.vue +233 -0
  120. package/src/components/SegmentedControl.vue +2 -2
  121. package/src/components/fileBrowserPresentation.ts +39 -0
  122. package/src/components/index.ts +5 -0
  123. package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
  124. package/src/components/internal/FileBrowserListingInternal.vue +197 -0
  125. package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
  126. package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
  127. package/src/components/internal/FilePickerTreeInternal.vue +238 -0
  128. package/src/composables/filePicker/localFiles.ts +58 -0
  129. package/src/composables/filePicker/usePickerNavigation.ts +220 -0
  130. package/src/composables/filePicker/usePickerSelection.ts +259 -0
  131. package/src/composables/filePicker/validation.ts +41 -0
  132. package/src/composables/index.ts +7 -0
  133. package/src/composables/useApi.ts +57 -13
  134. package/src/composables/useFileBrowser.ts +314 -0
  135. package/src/composables/useFilePicker.ts +366 -0
  136. package/src/composables/useGeneratedAnalysis.ts +10 -17
  137. package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
  138. package/src/composables/usePluginClient.ts +3 -0
  139. package/src/index.ts +3 -0
  140. package/src/styles/components/file-browser-modal.css +489 -0
  141. package/src/styles/components/file-picker.css +502 -0
  142. package/src/styles/components/icon-button.css +9 -0
  143. package/src/styles/index.css +3 -0
  144. package/src/types/fileBrowserTypes.ts +85 -0
  145. package/src/types/filePicker.ts +72 -0
  146. package/src/types/index.ts +14 -0
  147. package/dist/ExperimentPopover-oL0fg_A0.js.map +0 -1
  148. package/dist/components-CgAUzF8F.js.map +0 -1
  149. package/dist/composables-B_pia3hO.js.map +0 -1
  150. package/dist/errors-_W0-LbA6.js.map +0 -1
  151. package/dist/useControlSchema-aMZlb4Sc.js.map +0 -1
  152. package/dist/useProtocolTemplates-BmX_iVBR.js.map +0 -1
@@ -14,6 +14,7 @@ import type {
14
14
  } from './useJobsStatusTray'
15
15
  import { useEventStream, type EventStreamMessage } from './useEventStream'
16
16
  import { isActiveJobStatus, normalizeJobState } from '../jobs'
17
+ import { MintApiError, mintApiErrorFromResponse } from '../apiErrors'
17
18
  import type {
18
19
  GeneratedAnalysisJob,
19
20
  GeneratedAnalysisJobInput,
@@ -129,16 +130,6 @@ type PluginJobStreamData = GeneratedAnalysisJobInput | {
129
130
  jobs: GeneratedAnalysisJobInput[]
130
131
  }
131
132
 
132
- class GeneratedAnalysisRequestError extends Error {
133
- constructor(
134
- readonly status: number,
135
- readonly detail: unknown,
136
- ) {
137
- super(typeof detail === 'string' ? detail : `Request failed (${status})`)
138
- this.name = 'GeneratedAnalysisRequestError'
139
- }
140
- }
141
-
142
133
  export function createGeneratedAnalysisTransport(
143
134
  options: GeneratedAnalysisTransportOptions = {},
144
135
  ): GeneratedAnalysisTransport {
@@ -838,7 +829,7 @@ function createPluginJobs(
838
829
  job: GeneratedAnalysisJob,
839
830
  cause: unknown,
840
831
  ): void {
841
- if (cause instanceof GeneratedAnalysisRequestError) {
832
+ if (cause instanceof MintApiError) {
842
833
  if (cause.status === 404 || cause.status === 410) {
843
834
  discardCachedResult(job.job_id)
844
835
  jobs.value = jobs.value.map(candidate => (
@@ -1037,8 +1028,10 @@ function createPluginJobs(
1037
1028
  },
1038
1029
  })
1039
1030
  if (!response.ok) {
1040
- const payload = await response.json().catch(() => null) as { detail?: unknown } | null
1041
- throw new GeneratedAnalysisRequestError(response.status, payload?.detail)
1031
+ const payload: unknown = await response.json().catch(() => null)
1032
+ throw mintApiErrorFromResponse(response.status, payload, {
1033
+ requestId: response.headers.get('X-Request-ID'),
1034
+ })
1042
1035
  }
1043
1036
  return await response.json() as T
1044
1037
  }
@@ -1177,20 +1170,20 @@ function createGeneratedSessionStorage(candidate?: Storage): GeneratedSessionSto
1177
1170
  }
1178
1171
 
1179
1172
  function isSessionNotFoundError(error: unknown): boolean {
1180
- return error instanceof GeneratedAnalysisRequestError
1173
+ return error instanceof MintApiError
1181
1174
  && error.status === 404
1182
- && error.detail === SESSION_NOT_FOUND_DETAIL
1175
+ && (error.detail === SESSION_NOT_FOUND_DETAIL || error.message === SESSION_NOT_FOUND_DETAIL)
1183
1176
  }
1184
1177
 
1185
1178
  function isRetryableResultError(error: unknown): boolean {
1186
- return !(error instanceof GeneratedAnalysisRequestError)
1179
+ return !(error instanceof MintApiError)
1187
1180
  || error.status === 409
1188
1181
  || error.status === 429
1189
1182
  || error.status >= 500
1190
1183
  }
1191
1184
 
1192
1185
  function isRetryableJobLookupError(error: unknown): boolean {
1193
- return !(error instanceof GeneratedAnalysisRequestError)
1186
+ return !(error instanceof MintApiError)
1194
1187
  || error.status === 408
1195
1188
  || error.status === 429
1196
1189
  || error.status >= 500
@@ -0,0 +1,104 @@
1
+ import type { PickerAdapter, PickerNode, PickerRequest } from '../types/filePicker'
2
+ import type { FileDirectoryListing, ServerMount } from '../types/fileBrowserTypes'
3
+ import { useApi } from './useApi'
4
+ import { getInjectedPlatformContext } from './platformContextHelpers'
5
+ import { pickerFormat } from './filePicker/validation'
6
+
7
+ /** Opaque picker identity; decode before passing a selection to a mount-scoped API. */
8
+ export function encodePlatformPickerPath(mountId: string, path = ''): string {
9
+ return JSON.stringify([mountId, path])
10
+ }
11
+ export function decodePlatformPickerPath(path: string): { mountId: string; path: string } {
12
+ const decoded: unknown = JSON.parse(path)
13
+ if (
14
+ !Array.isArray(decoded) ||
15
+ decoded.length !== 2 ||
16
+ !decoded.every((item) => typeof item === 'string') ||
17
+ !decoded[0]
18
+ ) {
19
+ throw new Error('Invalid platform picker path.')
20
+ }
21
+ return { mountId: decoded[0], path: decoded[1] }
22
+ }
23
+
24
+ /** Adapt the authenticated MINT mount API without exposing absolute host paths. */
25
+ export function usePlatformFilePickerAdapter(options: { apiBaseUrl?: string } = {}): PickerAdapter {
26
+ const api = useApi({
27
+ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl,
28
+ })
29
+ const mountLabels = new Map<string, string>()
30
+ async function listRoot(request?: PickerRequest): Promise<PickerNode[]> {
31
+ try {
32
+ const { mounts } = await api.get<{ mounts: ServerMount[] }>('/filesystem/mounts', {
33
+ signal: request?.signal,
34
+ })
35
+ return mounts.map((mount) => {
36
+ mountLabels.set(mount.id, mount.label)
37
+ return {
38
+ kind: 'folder',
39
+ path: encodePlatformPickerPath(mount.id),
40
+ name: mount.label,
41
+ parentPath: null,
42
+ parentName: 'Server mounts',
43
+ unavailableReason: mount.available === false ? 'This mount is offline.' : undefined,
44
+ }
45
+ })
46
+ } catch (cause) {
47
+ if ((cause as { response?: { status?: number } }).response?.status === 404) return []
48
+ throw cause
49
+ }
50
+ }
51
+ async function listChildren(path: string, request?: PickerRequest): Promise<PickerNode[]> {
52
+ const location = decodePlatformPickerPath(path)
53
+ const query = new URLSearchParams({ mount_id: location.mountId, path: location.path })
54
+ const listing = await api.get<FileDirectoryListing>(`/filesystem/browse?${query}`, {
55
+ signal: request?.signal,
56
+ })
57
+ if (listing.truncated)
58
+ throw new Error(
59
+ 'This directory exceeds the server listing limit. Choose a smaller data directory.',
60
+ )
61
+ return (listing.entries ?? []).map(
62
+ (entry) =>
63
+ ({
64
+ kind: entry.kind === 'directory' ? 'folder' : 'file',
65
+ path: encodePlatformPickerPath(location.mountId, entry.path),
66
+ parentPath: path,
67
+ parentName: `${mountLabels.get(location.mountId) ?? location.mountId}${location.path ? ` / ${location.path}` : ''}`,
68
+ name: entry.name,
69
+ bytes: entry.size_bytes,
70
+ ...(entry.kind === 'directory'
71
+ ? { hasChildren: entry.child_count !== 0 }
72
+ : { format: pickerFormat(entry.name) }),
73
+ }) as PickerNode,
74
+ )
75
+ }
76
+ return {
77
+ listRoot,
78
+ listChildren,
79
+ async search(query, scope, request) {
80
+ const matches: PickerNode[] = []
81
+ const queue = [scope.path]
82
+ const visited = new Set<string>()
83
+ while (queue.length) {
84
+ request?.signal?.throwIfAborted()
85
+ const path = queue.shift()!
86
+ if (visited.has(path)) continue
87
+ visited.add(path)
88
+ if (visited.size > 500)
89
+ throw new Error(
90
+ 'Search reached its folder limit. Search a smaller location or turn off Subfolders.',
91
+ )
92
+ const nodes = path ? await listChildren(path, request) : await listRoot(request)
93
+ for (const node of nodes) {
94
+ if (node.name.toLowerCase().includes(query.toLowerCase())) matches.push(node)
95
+ if (scope.recursive && node.kind === 'folder' && !node.unavailableReason)
96
+ queue.push(node.path)
97
+ }
98
+ if (matches.length > 10000)
99
+ throw new Error('Too many search results. Use a more specific search.')
100
+ }
101
+ return matches
102
+ },
103
+ }
104
+ }
@@ -423,6 +423,7 @@ export function createPluginClient<TClient extends object = GeneratedPluginClien
423
423
  const api = useApi({
424
424
  baseUrl: parts.baseUrl,
425
425
  withAuth: endpoint.requiresAuth !== false,
426
+ typedErrors: true,
426
427
  })
427
428
  const config = endpointRequestConfig(endpoint, parts.query, callOptions)
428
429
 
@@ -469,6 +470,7 @@ export async function uploadPluginEndpoint<TResponse = unknown>(
469
470
  const api = useApi({
470
471
  baseUrl: parts.baseUrl,
471
472
  withAuth: endpoint.requiresAuth !== false,
473
+ typedErrors: true,
472
474
  })
473
475
  const body = endpoint.hasBody
474
476
  ? requestBodyFromPayload(payload, parts.requestPayload, endpoint)
@@ -507,6 +509,7 @@ export async function downloadPluginEndpoint(
507
509
  const api = useApi({
508
510
  baseUrl: parts.baseUrl,
509
511
  withAuth: endpoint.requiresAuth !== false,
512
+ typedErrors: true,
510
513
  })
511
514
  const config = {
512
515
  ...(endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {}),
package/src/index.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  // Styles (import to ensure bundling)
2
2
  import './styles/index.css'
3
3
 
4
+ // Typed API errors
5
+ export * from './apiErrors'
6
+
4
7
  // Vue Plugin for global component registration
5
8
  export { MINTSdk, default } from './install'
6
9
 
@@ -0,0 +1,489 @@
1
+ .mint-file-browser-dialog,
2
+ .mint-file-browser-dialog * {
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ /* File browser workspace. Actions use BaseButton/IconButton; these rules own layout. */
7
+ .mint-file-browser-overlay {
8
+ position: fixed;
9
+ inset: 0;
10
+ z-index: 50;
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ padding: 24px;
15
+ background: rgb(15 23 42 / 45%);
16
+ }
17
+
18
+ .mint-file-browser-dialog {
19
+ display: flex;
20
+ flex-direction: column;
21
+ width: 880px;
22
+ max-width: 100%;
23
+ max-height: calc(100dvh - 48px);
24
+ overflow: hidden;
25
+ border: 1px solid var(--border-color);
26
+ border-radius: var(--radius-lg);
27
+ background: var(--bg-secondary);
28
+ color: var(--text-primary);
29
+ box-shadow: var(--shadow-lg);
30
+ }
31
+
32
+ .mint-file-browser-dialog--sm {
33
+ width: 520px;
34
+ }
35
+ .mint-file-browser-dialog--md {
36
+ width: 640px;
37
+ }
38
+ .mint-file-browser-dialog--lg {
39
+ width: 760px;
40
+ }
41
+ .mint-file-browser-dialog--xl {
42
+ width: 840px;
43
+ }
44
+
45
+ .mint-file-browser-dialog__header {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 12px;
49
+ flex-shrink: 0;
50
+ padding: 16px 20px;
51
+ border-bottom: 1px solid var(--border-color);
52
+ }
53
+
54
+ .mint-file-browser__heading {
55
+ flex: 1;
56
+ min-width: 0;
57
+ }
58
+ .mint-file-browser__title {
59
+ margin: 0;
60
+ font-size: 17px;
61
+ font-weight: 600;
62
+ }
63
+ .mint-file-browser__context {
64
+ margin: 3px 0 0;
65
+ color: var(--text-secondary);
66
+ font-size: 12px;
67
+ overflow-wrap: anywhere;
68
+ }
69
+
70
+ .mint-file-browser {
71
+ display: flex;
72
+ flex-direction: column;
73
+ min-height: 0;
74
+ font-size: 13px;
75
+ }
76
+
77
+ .mint-file-browser__mounts {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 12px;
81
+ padding: 10px 20px 0;
82
+ flex-shrink: 0;
83
+ min-width: 0;
84
+ }
85
+
86
+ .mint-file-browser__mounts-label {
87
+ font-size: 12px;
88
+ color: var(--text-muted);
89
+ }
90
+
91
+ .mint-file-browser__mount-options {
92
+ display: flex;
93
+ gap: 6px;
94
+ min-width: 0;
95
+ overflow-x: auto;
96
+ padding: 2px;
97
+ }
98
+
99
+ .mint-file-browser__mount {
100
+ flex-shrink: 0;
101
+ max-width: 260px;
102
+ }
103
+ .mint-file-browser__mount-label {
104
+ overflow: hidden;
105
+ text-overflow: ellipsis;
106
+ }
107
+ .mint-file-browser__mount-count {
108
+ font-size: 11px;
109
+ color: var(--text-muted);
110
+ }
111
+
112
+ .mint-file-browser__toolbar {
113
+ padding: 8px 20px 12px;
114
+ flex-shrink: 0;
115
+ }
116
+ .mint-file-browser__pathbar,
117
+ .mint-file-browser__tools {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 8px;
121
+ min-width: 0;
122
+ }
123
+ .mint-file-browser__pathbar {
124
+ margin-bottom: 8px;
125
+ }
126
+ .mint-file-browser__crumbs {
127
+ display: flex;
128
+ align-items: center;
129
+ flex: 1;
130
+ min-width: 0;
131
+ overflow-x: auto;
132
+ }
133
+ .mint-file-browser__crumb {
134
+ flex-shrink: 0;
135
+ }
136
+ .mint-file-browser__crumb--current {
137
+ font-weight: 600;
138
+ }
139
+ .mint-file-browser__crumb-sep {
140
+ color: var(--text-muted);
141
+ }
142
+ .mint-file-browser__search {
143
+ flex: 1;
144
+ min-width: 100px;
145
+ }
146
+ .mint-file-browser__rules {
147
+ color: var(--text-muted);
148
+ font-family: var(--font-mono);
149
+ font-size: 11px;
150
+ max-width: 160px;
151
+ overflow: hidden;
152
+ text-overflow: ellipsis;
153
+ white-space: nowrap;
154
+ }
155
+ .mint-file-browser__select-folder {
156
+ flex-shrink: 0;
157
+ }
158
+
159
+ .mint-file-browser__listing {
160
+ display: flex;
161
+ flex-direction: column;
162
+ min-height: 0;
163
+ }
164
+ .mint-file-browser__head,
165
+ .mint-file-browser__row {
166
+ display: grid;
167
+ grid-template-columns: 16px 24px minmax(0, 1fr) 80px 72px 32px;
168
+ align-items: center;
169
+ gap: 10px;
170
+ padding: 4px 20px;
171
+ }
172
+ .mint-file-browser__head {
173
+ min-height: 32px;
174
+ border-block: 1px solid var(--border-color);
175
+ background: var(--bg-primary);
176
+ color: var(--text-secondary);
177
+ font-size: 11px;
178
+ font-weight: 500;
179
+ }
180
+ .mint-file-browser__col-size,
181
+ .mint-file-browser__col-type,
182
+ .mint-file-browser__size,
183
+ .mint-file-browser__type {
184
+ text-align: right;
185
+ }
186
+ .mint-file-browser__rows {
187
+ overflow-y: auto;
188
+ max-height: min(46dvh, 440px);
189
+ min-height: 0;
190
+ }
191
+ .mint-file-browser__row {
192
+ min-height: 42px;
193
+ border-bottom: 1px solid var(--border-light);
194
+ }
195
+ .mint-file-browser__row--dir {
196
+ cursor: pointer;
197
+ }
198
+ .mint-file-browser__row:hover {
199
+ background: var(--bg-hover);
200
+ }
201
+ .mint-file-browser__row--selected,
202
+ .mint-file-browser__row--selected:hover {
203
+ background: var(--color-primary-soft);
204
+ }
205
+ .mint-file-browser__name-cell {
206
+ display: flex;
207
+ align-items: center;
208
+ gap: 8px;
209
+ min-width: 0;
210
+ }
211
+ .mint-file-browser__name {
212
+ overflow: hidden;
213
+ text-overflow: ellipsis;
214
+ white-space: nowrap;
215
+ }
216
+ .mint-file-browser__row--dir .mint-file-browser__name {
217
+ font-weight: 500;
218
+ }
219
+ .mint-file-browser__note-pill {
220
+ flex-shrink: 0;
221
+ color: var(--color-primary);
222
+ font-size: 11px;
223
+ }
224
+ .mint-file-browser__size,
225
+ .mint-file-browser__type {
226
+ font-size: 12px;
227
+ color: var(--text-secondary);
228
+ }
229
+ .mint-file-browser__row--dimmed .mint-file-browser__name,
230
+ .mint-file-browser__row--dimmed .mint-file-browser__size,
231
+ .mint-file-browser__row--dimmed .mint-file-browser__type {
232
+ color: var(--text-muted);
233
+ }
234
+ .mint-file-browser__row--dimmed .mint-file-browser__row-icon {
235
+ filter: grayscale(1);
236
+ opacity: 0.6;
237
+ }
238
+ .mint-file-browser__checkbox {
239
+ width: 16px;
240
+ height: 16px;
241
+ margin: 0;
242
+ accent-color: var(--color-primary);
243
+ cursor: pointer;
244
+ }
245
+ .mint-file-browser__checkbox:focus-visible {
246
+ outline: none;
247
+ box-shadow: var(--focus-ring);
248
+ }
249
+ .mint-file-browser__row-icon {
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ color: var(--text-secondary);
254
+ }
255
+ .mint-file-browser__row-icon--directory {
256
+ color: var(--mint-info);
257
+ }
258
+ .mint-file-browser__icon {
259
+ width: 16px;
260
+ height: 16px;
261
+ flex-shrink: 0;
262
+ }
263
+ .mint-file-browser__skeleton {
264
+ padding: 16px 20px;
265
+ overflow: auto;
266
+ }
267
+ .mint-file-browser__skeleton-row {
268
+ display: flex;
269
+ justify-content: space-between;
270
+ gap: 12px;
271
+ padding: 10px 0;
272
+ }
273
+ .mint-file-browser__error {
274
+ display: flex;
275
+ align-items: center;
276
+ justify-content: space-between;
277
+ gap: 16px;
278
+ padding: 24px 20px;
279
+ color: var(--mint-error);
280
+ overflow: auto;
281
+ }
282
+ .mint-file-browser__listing > .mint-empty-state {
283
+ padding: 32px 20px;
284
+ overflow: auto;
285
+ }
286
+ .mint-file-browser__status {
287
+ display: flex;
288
+ align-items: center;
289
+ gap: 8px;
290
+ padding: 8px 20px;
291
+ font-size: 11px;
292
+ color: var(--text-muted);
293
+ flex-wrap: wrap;
294
+ flex-shrink: 0;
295
+ }
296
+ .mint-file-browser__status-spacer {
297
+ flex: 1;
298
+ }
299
+
300
+ .mint-file-browser-dialog__footer {
301
+ padding: 12px 20px;
302
+ border-top: 1px solid var(--border-color);
303
+ flex-shrink: 0;
304
+ }
305
+ .mint-file-browser__tray {
306
+ display: flex;
307
+ align-items: center;
308
+ gap: 20px;
309
+ min-width: 0;
310
+ }
311
+ .mint-file-browser__tray-main {
312
+ flex: 1;
313
+ min-width: 0;
314
+ }
315
+ .mint-file-browser__tray-head {
316
+ display: flex;
317
+ align-items: center;
318
+ gap: 8px;
319
+ flex-wrap: wrap;
320
+ min-height: 32px;
321
+ }
322
+ .mint-file-browser__tray-count {
323
+ font-size: 13px;
324
+ font-weight: 600;
325
+ }
326
+ .mint-file-browser__tray-summary,
327
+ .mint-file-browser__selection-hint {
328
+ font-size: 12px;
329
+ color: var(--text-muted);
330
+ }
331
+ .mint-file-browser__chips {
332
+ display: flex;
333
+ flex-wrap: wrap;
334
+ gap: 6px;
335
+ max-height: 84px;
336
+ overflow-y: auto;
337
+ margin-top: 4px;
338
+ }
339
+ .mint-file-browser__chip {
340
+ display: inline-flex;
341
+ align-items: center;
342
+ gap: 6px;
343
+ max-width: 100%;
344
+ padding-left: 8px;
345
+ background: var(--bg-primary);
346
+ border: 1px solid var(--border-color);
347
+ border-radius: var(--radius-md);
348
+ font-size: 12px;
349
+ }
350
+ .mint-file-browser__chip-label {
351
+ overflow: hidden;
352
+ text-overflow: ellipsis;
353
+ white-space: nowrap;
354
+ }
355
+ .mint-file-browser__chip-remove {
356
+ flex-shrink: 0;
357
+ }
358
+ .mint-file-browser__actions {
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 8px;
362
+ flex-shrink: 0;
363
+ }
364
+
365
+ @media (max-width: 600px) {
366
+ .mint-file-browser-overlay {
367
+ padding: 8px;
368
+ }
369
+ .mint-file-browser-dialog {
370
+ max-height: calc(100dvh - 16px);
371
+ }
372
+ .mint-file-browser-dialog__header {
373
+ padding: 12px;
374
+ gap: 8px;
375
+ flex-wrap: wrap;
376
+ }
377
+ .mint-file-browser__title {
378
+ font-size: 15px;
379
+ }
380
+ .mint-file-browser__local {
381
+ order: 3;
382
+ }
383
+ .mint-file-browser__mounts {
384
+ padding: 8px 12px 0;
385
+ }
386
+ .mint-file-browser__mounts-label {
387
+ display: none;
388
+ }
389
+ .mint-file-browser__toolbar {
390
+ padding: 6px 12px 10px;
391
+ }
392
+ .mint-file-browser__tools {
393
+ flex-wrap: wrap;
394
+ gap: 6px;
395
+ }
396
+ .mint-file-browser__search {
397
+ flex-basis: calc(100% - 42px);
398
+ }
399
+ .mint-file-browser__rules {
400
+ order: 2;
401
+ flex: 1;
402
+ max-width: none;
403
+ }
404
+ .mint-file-browser__select-folder {
405
+ order: 3;
406
+ margin-left: auto;
407
+ }
408
+ .mint-file-browser__head,
409
+ .mint-file-browser__row {
410
+ grid-template-columns: 16px 20px minmax(0, 1fr) 64px 32px;
411
+ gap: 6px;
412
+ padding-inline: 12px;
413
+ }
414
+ .mint-file-browser__col-type,
415
+ .mint-file-browser__type {
416
+ display: none;
417
+ }
418
+ .mint-file-browser__name-cell {
419
+ flex-direction: column;
420
+ align-items: flex-start;
421
+ gap: 0;
422
+ }
423
+ .mint-file-browser__name {
424
+ max-width: 100%;
425
+ }
426
+ .mint-file-browser__status {
427
+ padding-inline: 12px;
428
+ }
429
+ .mint-file-browser__legend {
430
+ display: none;
431
+ }
432
+ .mint-file-browser-dialog__footer {
433
+ padding: 10px 12px;
434
+ }
435
+ .mint-file-browser__tray {
436
+ flex-direction: column;
437
+ align-items: stretch;
438
+ gap: 10px;
439
+ }
440
+ .mint-file-browser__actions {
441
+ justify-content: flex-end;
442
+ }
443
+ .mint-file-browser__chips {
444
+ max-height: 80px;
445
+ }
446
+ }
447
+
448
+ /* Short windows scroll the workspace while keeping confirmation reachable. */
449
+ @media (max-height: 600px) {
450
+ .mint-file-browser {
451
+ overflow-y: auto;
452
+ }
453
+ .mint-file-browser__listing {
454
+ flex-shrink: 0;
455
+ }
456
+ .mint-file-browser__rows {
457
+ max-height: none;
458
+ }
459
+ .mint-file-browser__chips {
460
+ max-height: 40px;
461
+ }
462
+ }
463
+
464
+ @media (max-height: 600px) and (min-width: 601px) {
465
+ .mint-file-browser-overlay {
466
+ padding: 8px;
467
+ }
468
+ .mint-file-browser-dialog {
469
+ max-height: calc(100dvh - 16px);
470
+ }
471
+ .mint-file-browser-dialog__header {
472
+ padding-block: 8px;
473
+ }
474
+ .mint-file-browser__context {
475
+ display: none;
476
+ }
477
+ .mint-file-browser__toolbar {
478
+ display: grid;
479
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr);
480
+ gap: 8px;
481
+ padding-block: 6px;
482
+ }
483
+ .mint-file-browser__pathbar {
484
+ margin: 0;
485
+ }
486
+ .mint-file-browser__rules {
487
+ display: none;
488
+ }
489
+ }