@tzyc/dsh-model-visibility 1.9.0 → 1.10.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.
@@ -0,0 +1,221 @@
1
+ /**
2
+ * The online capability verification panel.
3
+ *
4
+ * The user picks a checker route (provider + model) and a write-back switch,
5
+ * runs the sweep, and the panel polls the Host for progress and the checker's
6
+ * token spend. The last route choice is kept in browser storage only: the
7
+ * Host persists run facts, not UI selections.
8
+ *
9
+ * @module client-src/checker-view
10
+ */
11
+
12
+ const React = require('react')
13
+ const h = React.createElement
14
+
15
+ import { formatTokens, checkerRoutesOf } from './pure.mjs'
16
+ import { useSource } from './hooks.mjs'
17
+
18
+ const PICK_KEY = 'dsh-model-visibility:checker-pick:v1'
19
+
20
+ function readPick() {
21
+ try {
22
+ const parsed = JSON.parse(window.localStorage.getItem(PICK_KEY))
23
+ return parsed !== null && typeof parsed === 'object'
24
+ ? {
25
+ provider: typeof parsed.provider === 'string' ? parsed.provider : '',
26
+ model: typeof parsed.model === 'string' ? parsed.model : '',
27
+ writeBack: parsed.writeBack !== false,
28
+ }
29
+ : { provider: '', model: '', writeBack: true }
30
+ } catch (_error) {
31
+ return { provider: '', model: '', writeBack: true }
32
+ }
33
+ }
34
+
35
+ function persistPick(pick) {
36
+ try {
37
+ window.localStorage.setItem(PICK_KEY, JSON.stringify(pick))
38
+ } catch (_error) {
39
+ // Storage availability is optional for this preference.
40
+ }
41
+ }
42
+
43
+ export function VerifyPanel({ verify, catalog, t, canUse }) {
44
+ const state = useSource(verify)
45
+ const [pick, setPick] = React.useState(readPick)
46
+ const [message, setMessage] = React.useState(null)
47
+ const [open, setOpen] = React.useState(false)
48
+
49
+ React.useEffect(() => {
50
+ verify.load()
51
+ }, [verify])
52
+
53
+ const routeOptions = checkerRoutesOf(catalog?.groups)
54
+ const route = routeOptions.find((entry) => entry.id === pick.provider) ?? null
55
+ const progress = state.snapshot?.state
56
+ const running = progress?.running === true
57
+ const finished = progress?.phase === 'done' || progress?.phase === 'cancelled'
58
+ const models = (catalog?.groups ?? [])
59
+ .find((group) => group.id === pick.provider)?.models ?? []
60
+
61
+ if (!canUse) {
62
+ return h('div', { className: 'mvp_fallback' }, t('checker.hostStale'))
63
+ }
64
+
65
+ const tokensSpent = progress ? formatTokens((progress.tokenUsage?.[0] ?? 0) + (progress.tokenUsage?.[1] ?? 0)) : null
66
+
67
+ const commit = (next) => {
68
+ setPick(next)
69
+ persistPick(next)
70
+ }
71
+
72
+ const run = () => {
73
+ setMessage(null)
74
+ verify
75
+ .start({ provider: pick.provider, model: pick.model, writeBack: pick.writeBack })
76
+ .then((result) => {
77
+ if (result !== undefined && result.ok === false) {
78
+ setMessage(result.message ?? t('checker.failed'))
79
+ }
80
+ })
81
+ .catch((error) => setMessage(String(error?.message ?? error)))
82
+ }
83
+
84
+ const cancel = () => {
85
+ verify.cancel().catch((error) => setMessage(String(error?.message ?? error)))
86
+ }
87
+
88
+ return h(
89
+ 'div',
90
+ { className: 'mvp_checker' + (open ? '' : ' mvp_checker--closed') },
91
+ h(
92
+ 'button',
93
+ {
94
+ type: 'button',
95
+ className: 'mvp_checkerHead',
96
+ 'aria-expanded': open,
97
+ onClick: () => setOpen(!open),
98
+ },
99
+ h('span', { className: 'mvp_checkerTitle' }, t('checker.title')),
100
+ h(
101
+ 'span',
102
+ { className: 'mvp_checkerSummary' },
103
+ running
104
+ ? t('checker.running', {
105
+ done: progress?.done ?? 0,
106
+ total: progress?.total ?? 0,
107
+ phase: t(`checker.phase.${progress?.phase ?? 'checker'}`),
108
+ })
109
+ : finished
110
+ ? t('checker.finished', { done: progress?.done ?? 0, total: progress?.total ?? 0, tokens: tokensSpent })
111
+ : t('checker.idle'),
112
+ ),
113
+ ),
114
+ open
115
+ ? h(
116
+ 'div',
117
+ { className: 'mvp_checkerBody' },
118
+ h('div', { className: 'mvp_checkerHint' }, t('checker.description')),
119
+ h(
120
+ 'div',
121
+ { className: 'mvp_checkerForm' },
122
+ h(
123
+ 'label',
124
+ { className: 'mvp_field' },
125
+ h('span', { className: 'mvp_fieldLabel' }, t('checker.provider')),
126
+ h(
127
+ 'select',
128
+ {
129
+ className: 'mvp_select',
130
+ value: pick.provider,
131
+ 'aria-label': t('checker.provider'),
132
+ onChange: (event) => commit({ ...pick, provider: event.target.value, model: '' }),
133
+ },
134
+ h('option', { key: '', value: '' }, t('override.kindAuto')),
135
+ routeOptions.map((entry) => h('option', { key: entry.id, value: entry.id }, entry.name)),
136
+ ),
137
+ ),
138
+ h(
139
+ 'label',
140
+ { className: 'mvp_field' },
141
+ h('span', { className: 'mvp_fieldLabel' }, t('checker.model')),
142
+ h(
143
+ 'select',
144
+ {
145
+ className: 'mvp_select',
146
+ value: pick.model,
147
+ 'aria-label': t('checker.model'),
148
+ disabled: route === null,
149
+ onChange: (event) => commit({ ...pick, model: event.target.value }),
150
+ },
151
+ h('option', { key: '', value: '' }, t('override.kindAuto')),
152
+ models.map((model) =>
153
+ h('option', { key: model.id, value: model.id }, model.name ?? model.id),
154
+ ),
155
+ ),
156
+ ),
157
+ ),
158
+ h(
159
+ 'div',
160
+ { className: 'mvp_checkerActions' },
161
+ h(
162
+ 'button',
163
+ {
164
+ type: 'button',
165
+ role: 'switch',
166
+ className: 'mvp_switch',
167
+ 'aria-checked': pick.writeBack,
168
+ title: t('checker.writeBackTitle'),
169
+ disabled: running,
170
+ onClick: () => commit({ ...pick, writeBack: !pick.writeBack }),
171
+ },
172
+ h('span', { className: 'mvp_thumb' }),
173
+ ),
174
+ h(
175
+ 'button',
176
+ {
177
+ type: 'button',
178
+ className: 'mvp_button',
179
+ title: t('checker.writeBackTitle'),
180
+ onClick: () => commit({ ...pick, writeBack: !pick.writeBack }),
181
+ },
182
+ t('checker.writeBack'),
183
+ ),
184
+ h(
185
+ 'button',
186
+ {
187
+ type: 'button',
188
+ className: running ? 'mvp_button' : 'mvp_button mvp_button--accent',
189
+ disabled: running ? false : pick.provider === '' || pick.model === '',
190
+ title: running ? t('checker.cancelTitle') : t('checker.startTitle'),
191
+ onClick: running ? cancel : run,
192
+ },
193
+ running ? t('checker.cancel') : t('checker.start'),
194
+ ),
195
+ ),
196
+ progress !== null && (running || finished)
197
+ ? h(
198
+ 'div',
199
+ { className: 'mvp_checkerProgress' },
200
+ t('checker.progress', {
201
+ done: progress?.done ?? 0,
202
+ total: progress?.total ?? 0,
203
+ tokens: tokensSpent,
204
+ }),
205
+ )
206
+ : null,
207
+ (progress?.errors?.length ?? 0) > 0
208
+ ? h(
209
+ 'div',
210
+ {
211
+ className: 'mvp_usageHint mvp_usageHint--warn',
212
+ title: progress.errors.join('\n'),
213
+ },
214
+ t('checker.errors', { count: progress.errors.length }),
215
+ )
216
+ : null,
217
+ message !== null ? h('div', { className: 'mvp_usageHint mvp_usageHint--warn' }, message) : null,
218
+ )
219
+ : null,
220
+ )
221
+ }
@@ -0,0 +1,153 @@
1
+ import { keyOf } from './format.mjs'
2
+ import { FIELD } from './constants.mjs'
3
+
4
+ function hiddenListOf(snapshot) {
5
+ const value = snapshot && snapshot.value
6
+ return value && Array.isArray(value[FIELD]) ? value[FIELD] : []
7
+ }
8
+
9
+ /**
10
+ * Rewrite one directory store in place so its snapshots omit hidden models.
11
+ *
12
+ * The shipped composer seat may capture the directory store before this plugin
13
+ * applies, so replacing the store object would never reach it; rewriting
14
+ * getSnapshot/subscribe on the captured object does. The session's active
15
+ * model stays listed even while hidden, because the shipped trigger
16
+ * resolves its label from this same catalog.
17
+ */
18
+ export function patchDirectoryStore(store, form) {
19
+ const readBase = store.getSnapshot.bind(store)
20
+ const subscribeBase = store.subscribe.bind(store)
21
+ const originalGet = store.getSnapshot
22
+ const originalSubscribe = store.subscribe
23
+ const listeners = new Set()
24
+ let snapshot = null
25
+ let lastBase
26
+ let lastSignature
27
+
28
+ const build = (baseSnapshot) => {
29
+ const hidden = new Set(hiddenListOf(form.getSnapshot()))
30
+ const activeSelection = baseSnapshot.current
31
+ const groups = []
32
+ for (const group of baseSnapshot.groups || []) {
33
+ const all = group.models || []
34
+ const models = []
35
+ for (const model of all) {
36
+ const key = keyOf(group.id, model.id)
37
+ const active =
38
+ activeSelection !== null &&
39
+ activeSelection !== undefined &&
40
+ activeSelection.provider === group.id &&
41
+ activeSelection.model === model.id
42
+ if (!hidden.has(key) || active) models.push(model)
43
+ }
44
+ if (models.length === 0) continue
45
+ groups.push(models.length === all.length ? group : { ...group, models })
46
+ }
47
+ return { ...baseSnapshot, groups }
48
+ }
49
+
50
+ const current = () => {
51
+ const baseSnapshot = readBase()
52
+ const signature = hiddenListOf(form.getSnapshot()).join('\u0000')
53
+ if (snapshot !== null && baseSnapshot === lastBase && signature === lastSignature) {
54
+ return snapshot
55
+ }
56
+ lastBase = baseSnapshot
57
+ lastSignature = signature
58
+ snapshot = build(baseSnapshot)
59
+ return snapshot
60
+ }
61
+
62
+ const emit = () => {
63
+ const previous = snapshot
64
+ const next = current()
65
+ if (previous === next) return
66
+ for (const listener of Array.from(listeners)) listener()
67
+ }
68
+
69
+ Object.defineProperties(store, {
70
+ __modelVisibilityPatched: { value: true, configurable: true },
71
+ getSnapshot: { value: current, configurable: true, writable: true },
72
+ subscribe: {
73
+ value(listener) {
74
+ listeners.add(listener)
75
+ const stop = subscribeBase(listener)
76
+ return () => {
77
+ listeners.delete(listener)
78
+ stop()
79
+ }
80
+ },
81
+ configurable: true,
82
+ writable: true,
83
+ },
84
+ })
85
+
86
+ return {
87
+ emit,
88
+ restore() {
89
+ Object.defineProperties(store, {
90
+ getSnapshot: { value: originalGet, configurable: true, writable: true },
91
+ subscribe: { value: originalSubscribe, configurable: true, writable: true },
92
+ })
93
+ delete store.__modelVisibilityPatched
94
+ listeners.clear()
95
+ },
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Patch every model directory — the ones already live when the plugin
101
+ * applies and every directory resolved later — so the shipped seat keeps
102
+ * owning the trigger, the Model / Effort rows, and third-party menu
103
+ * enhancements such as the reasoning-effort slider.
104
+ */
105
+ export function installComposerFilter(scope, form) {
106
+ const models = scope.modelDirectories
107
+ const original = models.directoryFor
108
+ if (typeof original !== 'function') return
109
+ const records = new Set()
110
+
111
+ const ensure = (directory) => {
112
+ if (directory === undefined || directory === null) return directory
113
+ const store = directory.store
114
+ if (store === undefined || store === null || store.__modelVisibilityPatched === true) {
115
+ return directory
116
+ }
117
+ try {
118
+ records.add(patchDirectoryStore(store, form))
119
+ } catch (_error) {
120
+ // Fail open: an unwritable store simply keeps showing every model.
121
+ }
122
+ return directory
123
+ }
124
+
125
+ scope.effect(() => {
126
+ const live = models.live && models.live.directories ? models.live.directories.values : null
127
+ if (live instanceof Set) for (const directory of live) ensure(directory)
128
+
129
+ const stopForm = form.subscribe(() => {
130
+ for (const record of records) record.emit()
131
+ })
132
+
133
+ const wrapper = function directoryFor(sessionId) {
134
+ return ensure(original.call(this, sessionId))
135
+ }
136
+ Object.defineProperty(models, 'directoryFor', {
137
+ value: wrapper,
138
+ configurable: true,
139
+ writable: true,
140
+ })
141
+
142
+ return () => {
143
+ stopForm()
144
+ for (const record of records) record.restore()
145
+ records.clear()
146
+ if (Object.getOwnPropertyDescriptor(models, 'directoryFor')?.value === wrapper) {
147
+ delete models.directoryFor
148
+ }
149
+ }
150
+ }, 'model-visibility: filtered model directory')
151
+ }
152
+
153
+ /* ----------------------------------------------------- settings page */
@@ -0,0 +1,20 @@
1
+ /** Dictionary namespace owned by this plugin. */
2
+ export const NS = 'model-visibility'
3
+ /** Host entry id from cordis.patch.yml; also the settings namespace. */
4
+ export const ENTRY = 'model-visibility'
5
+ /** Config field persisted by the Host settings document. */
6
+ export const FIELD = 'hidden'
7
+ /** Settings section id. */
8
+ export const SECTION_ID = 'model-visibility'
9
+ /**
10
+ * Same-origin route the Host half registers through `connection.fetch`.
11
+ * Resolved against the page base so a reverse-proxy prefix is preserved.
12
+ */
13
+ export const API = (() => {
14
+ if (typeof document === 'undefined') return '/api/dsh-model-visibility'
15
+ try {
16
+ return new URL('api/dsh-model-visibility', document.baseURI).pathname
17
+ } catch (_error) {
18
+ return '/api/dsh-model-visibility'
19
+ }
20
+ })()
@@ -0,0 +1,248 @@
1
+ export const CSS = `
2
+ .mvp_page{box-sizing:border-box;width:100%;height:100%;min-height:0;flex-direction:column;display:flex;overflow-y:auto;padding-bottom:24px}
3
+ .mvp_head{flex:none;flex-direction:column;gap:6px;padding:4px 0 14px;display:flex}
4
+ .mvp_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}
5
+ .mvp_desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
6
+ .mvp_toolbar{border-bottom:.5px solid var(--dsw-alias-border-l2);flex:none;justify-content:space-between;align-items:center;gap:12px;padding:10px 0;display:flex}
7
+ .mvp_summary{color:var(--dsw-alias-label-secondary);flex:1;min-width:0;font-size:12px;line-height:18px}
8
+ .mvp_actions{flex:none;align-items:center;justify-content:flex-end;gap:8px;display:flex;flex-wrap:wrap}
9
+ .mvp_button{box-sizing:border-box;height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,6px);padding:0 10px;font-family:inherit;font-size:12px;line-height:18px}
10
+ .mvp_button--accent{color:var(--dsw-alias-brand-primary);border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, transparent)}
11
+ .mvp_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
12
+ .mvp_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
13
+ .mvp_button:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
14
+ .mvp_group{flex:none;flex-direction:column;display:flex}
15
+ .mvp_groupHead{justify-content:space-between;align-items:center;gap:12px;padding:14px 0 6px;display:flex}
16
+ .mvp_groupTitle{color:var(--dsw-alias-label-primary);align-items:baseline;row-gap:2px;gap:8px;font-size:14px;font-weight:500;line-height:22px;display:flex;flex-wrap:wrap}
17
+ .mvp_groupName{min-width:0}
18
+ .mvp_collapse{box-sizing:border-box;flex:none;align-self:center;width:18px;height:18px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:4px;justify-content:center;align-items:center;padding:0;font-family:inherit;font-size:11px;line-height:18px;display:inline-flex}
19
+ .mvp_collapse:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
20
+ .mvp_collapse:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
21
+ .mvp_groupCount{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}
22
+ .mvp_row{border-bottom:.5px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:center;gap:16px;padding:10px 0;display:flex}
23
+ .mvp_row--gen{background:transparent;background:color-mix(in srgb, var(--dsw-alias-brand-primary) 4%, transparent)}
24
+ .mvp_row--kind-image{box-shadow:inset 2px 0 0 color-mix(in srgb, var(--dsw-alias-state-business-primary) 70%, transparent);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 4%, transparent)}
25
+ .mvp_row--kind-video{box-shadow:inset 2px 0 0 color-mix(in srgb, var(--dsw-alias-state-success-primary) 70%, transparent);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 4%, transparent)}
26
+ .mvp_row--kind-embedding{box-shadow:inset 2px 0 0 color-mix(in srgb, var(--dsw-alias-state-warn-primary) 70%, transparent);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 4%, transparent)}
27
+ .mvp_row--kind-rerank{box-shadow:inset 2px 0 0 color-mix(in srgb, var(--dsw-alias-state-error-primary) 60%, transparent);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 3%, transparent)}
28
+ .mvp_row--kind-audio{box-shadow:inset 2px 0 0 color-mix(in srgb, color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, var(--dsw-alias-state-error-primary)) 70%, transparent);background:transparent;background:color-mix(in srgb, color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, var(--dsw-alias-state-error-primary)) 4%, transparent)}
29
+ .mvp_row--kind-classifier{box-shadow:inset 2px 0 0 color-mix(in srgb, var(--dsw-alias-state-error-secondary) 60%, transparent);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-error-secondary) 3%, transparent)}
30
+ .mvp_row--kind-translation{box-shadow:inset 2px 0 0 var(--dsw-alias-border-l2)}
31
+ .mvp_rowText{flex-direction:column;gap:2px;min-width:0;display:flex}
32
+ .mvp_rowTitle{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
33
+ .mvp_rowMeta{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px;overflow:hidden}
34
+ .mvp_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;padding:6px 0}
35
+ .mvp_switch{position:relative;flex:none;box-sizing:border-box;width:36px;height:20px;padding:0;border:0;border-radius:999px;background:var(--dsw-alias-border-l2);cursor:pointer;transition:background-color .15s ease}
36
+ .mvp_switch[aria-checked="true"]{background:var(--dsw-alias-bg-base)}
37
+ .mvp_switch:disabled{opacity:.45;cursor:default}
38
+ .mvp_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
39
+ .mvp_thumb{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:50%;background:var(--dsw-alias-label-secondary);transition:transform .15s ease,background-color .15s ease}
40
+ .mvp_switch[aria-checked="true"] .mvp_thumb{background:var(--dsw-alias-label-primary);transform:translateX(16px)}
41
+ .mvp_tags{flex-wrap:wrap;align-items:center;gap:4px;margin-top:4px;display:flex}
42
+ .mvp_tag{box-sizing:border-box;max-width:100%;height:18px;color:var(--dsw-alias-label-secondary);white-space:nowrap;background:0 0;border:.5px solid var(--dsw-alias-border-l2);border-radius:4px;align-items:center;padding:0 6px;font-size:11px;line-height:16px;display:inline-flex;overflow:hidden;text-overflow:ellipsis}
43
+ .mvp_tag--accent{color:var(--dsw-alias-brand-primary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-brand-primary) 10%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent)}
44
+ .mvp_tag--vision{color:var(--dsw-alias-state-business-primary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-business-primary) 45%, transparent)}
45
+ .mvp_tag--kind{font-weight:500}
46
+ .mvp_tag--kind-image{color:var(--dsw-alias-state-business-primary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-business-primary) 55%, transparent)}
47
+ .mvp_tag--kind-video{color:var(--dsw-alias-state-success-primary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent)}
48
+ .mvp_tag--kind-embedding{color:var(--dsw-alias-state-warn-primary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 55%, transparent)}
49
+ .mvp_tag--kind-rerank{color:var(--dsw-alias-state-error-primary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-error-primary) 50%, transparent)}
50
+ .mvp_tag--kind-audio{color:color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, var(--dsw-alias-state-error-primary));background:transparent;background:color-mix(in srgb, color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, var(--dsw-alias-state-error-primary)) 12%, transparent);border-color:color-mix(in srgb, color-mix(in srgb, var(--dsw-alias-state-business-primary) 50%, var(--dsw-alias-state-error-primary)) 50%, transparent)}
51
+ .mvp_tag--kind-classifier{color:var(--dsw-alias-state-error-secondary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-error-secondary) 12%, transparent);border-color:color-mix(in srgb, var(--dsw-alias-state-error-secondary) 55%, transparent)}
52
+ .mvp_tag--kind-translation{color:var(--dsw-alias-label-tertiary);border-color:var(--dsw-alias-border-l2)}
53
+ .mvp_tag--kind-unknown{color:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-border-l2)}
54
+ .mvp_tag--inferred{border-style:dashed}
55
+ .mvp_tag--muted{color:var(--dsw-alias-label-tertiary);border-color:var(--dsw-alias-border-l1)}
56
+ .mvp_groupCap{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:400;line-height:16px}
57
+ .mvp_kindCount--image{color:var(--dsw-alias-state-business-primary)}
58
+ .mvp_kindCount--video{color:var(--dsw-alias-state-success-primary)}
59
+ .mvp_kindCount--other{color:var(--dsw-alias-state-warn-primary)}
60
+ .mvp_applyRow{flex:none;align-items:flex-start;gap:10px;margin:8px 0 2px;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,6px);padding:8px 10px;display:flex;background:transparent;background:color-mix(in srgb, var(--dsw-alias-brand-primary) 4%, transparent)}
61
+ .mvp_applyText{flex-direction:column;gap:2px;min-width:0;display:flex}
62
+ .mvp_applyTitle{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:18px}
63
+ .mvp_applyHint{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
64
+ .mvp_applyRow .mvp_switch{margin-top:1px}
65
+ .mvp_groupProbe{color:var(--dsw-alias-label-tertiary);border:.5px dashed var(--dsw-alias-border-l2);border-radius:4px;max-width:100%;white-space:nowrap;text-overflow:ellipsis;padding:0 5px;font-size:10px;line-height:15px;overflow:hidden}
66
+ .mvp_filters{align-items:center;gap:6px;padding:8px 0 2px;display:flex;flex-wrap:wrap}
67
+ .mvp_filterLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
68
+ .mvp_search{align-items:center;gap:6px;display:flex;min-width:220px;flex:1 1 220px}
69
+ .mvp_searchInput{box-sizing:border-box;width:100%;height:28px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,6px);padding:0 8px;font-family:inherit;font-size:12px;line-height:18px}
70
+ .mvp_searchInput:focus-visible,.mvp_contextSelect:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
71
+ .mvp_context{align-items:center;gap:6px;display:flex}
72
+ .mvp_contextSelect{box-sizing:border-box;height:24px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-base);border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,6px);padding:0 6px;font-family:inherit;font-size:11px;line-height:18px}
73
+ .mvp_filter{box-sizing:border-box;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:.5px solid var(--dsw-alias-border-l2);border-radius:999px;padding:0 9px;font-family:inherit;font-size:11px;line-height:18px}
74
+ .mvp_filter:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
75
+ .mvp_filter[aria-pressed="true"]{color:var(--dsw-alias-brand-primary);border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, transparent);background:transparent;background:color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)}
76
+ .mvp_filter:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
77
+ .mvp_note{color:var(--dsw-alias-label-tertiary);flex:none;padding:6px 0 0;font-size:10px;line-height:15px}
78
+ .mvp_legend{color:var(--dsw-alias-label-tertiary);flex:none;padding:8px 0 2px;font-size:11px;line-height:16px}
79
+ .mvp_fallback{color:var(--dsw-alias-label-secondary);background:transparent;background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent);border-radius:var(--dsw-radius-sm,6px);padding:6px 8px;font-size:11px;line-height:16px;margin-top:8px}
80
+ .mvp_rowActions{flex:none;align-items:center;gap:8px;display:flex}
81
+ .mvp_editButton{height:24px;padding:0 8px;font-size:11px;line-height:18px}
82
+ .mvp_editButton[aria-pressed="true"]{color:var(--dsw-alias-brand-primary);border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, transparent)}
83
+ .mvp_editor{box-sizing:border-box;border:.5px solid color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, transparent);border-radius:var(--dsw-radius-sm,6px);background:transparent;background:color-mix(in srgb, var(--dsw-alias-brand-primary) 4%, transparent);margin:2px 0 12px;padding:10px 12px;flex-direction:column;gap:10px;display:flex}
84
+ .mvp_editorHead{flex-direction:column;gap:2px;display:flex}
85
+ .mvp_editorTitle{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:18px}
86
+ .mvp_editorSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
87
+ .mvp_editorGrid{flex-wrap:wrap;align-items:flex-end;gap:10px 16px;display:flex}
88
+ .mvp_field{flex-direction:column;gap:4px;min-width:140px;display:flex}
89
+ .mvp_field--wide{flex:1 1 100%}
90
+ .mvp_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}
91
+ .mvp_select,.mvp_number{box-sizing:border-box;height:26px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,6px);padding:0 8px;font-family:inherit;font-size:12px;line-height:18px}
92
+ .mvp_number{width:180px}
93
+ .mvp_select:focus-visible,.mvp_number:focus-visible,.mvp_chip:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
94
+ .mvp_chips{flex-wrap:wrap;align-items:center;gap:6px;display:flex}
95
+ .mvp_chip{box-sizing:border-box;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:.5px solid var(--dsw-alias-border-l2);border-radius:999px;padding:0 10px;font-family:inherit;font-size:11px;line-height:18px}
96
+ .mvp_chip:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}
97
+ .mvp_chip[aria-pressed="true"]{color:var(--dsw-alias-brand-primary);border-color:color-mix(in srgb, var(--dsw-alias-brand-primary) 55%, transparent);background:transparent;background:color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)}
98
+ .mvp_chip:disabled{opacity:.45;cursor:default}
99
+ .mvp_editorFooter{justify-content:space-between;align-items:center;gap:12px;display:flex;flex-wrap:wrap}
100
+ .mvp_editorNote{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:15px;flex:1 1 220px;min-width:0}
101
+ .mvp_check{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer}
102
+ .mvp_check input{accent-color:var(--dsw-alias-brand-primary, #4c8dff);margin:0}
103
+ .mvp_editorActions{flex:none;align-items:center;gap:6px;display:flex;flex-wrap:wrap}
104
+ /* ---------------------------------------------------- usage & checker */
105
+
106
+ .mvp_usage {
107
+ margin: 0 0 12px;
108
+ padding: 10px 12px;
109
+ border: 1px solid var(--dsw-alias-border, transparent);
110
+ border-radius: 10px;
111
+ background: transparent;
112
+ background: color-mix(in srgb, currentColor 4%, transparent);
113
+ }
114
+
115
+ .mvp_usage--off {
116
+ opacity: 0.75;
117
+ }
118
+
119
+ .mvp_usageHead {
120
+ display: flex;
121
+ align-items: center;
122
+ gap: 10px;
123
+ }
124
+
125
+ .mvp_usageTitle {
126
+ font-weight: 600;
127
+ font-size: 12px;
128
+ }
129
+
130
+ .mvp_usageComputing {
131
+ color: var(--dsw-alias-text-tertiary, currentColor);
132
+ font-size: 11px;
133
+ }
134
+
135
+ .mvp_usageActions {
136
+ display: flex;
137
+ gap: 8px;
138
+ margin-left: auto;
139
+ }
140
+
141
+ .mvp_usageRanges {
142
+ display: flex;
143
+ gap: 18px;
144
+ flex-wrap: wrap;
145
+ margin-top: 8px;
146
+ }
147
+
148
+ .mvp_stat {
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 2px;
152
+ }
153
+
154
+ .mvp_statLabel {
155
+ font-size: 11px;
156
+ color: var(--dsw-alias-text-tertiary, currentColor);
157
+ }
158
+
159
+ .mvp_statValue {
160
+ font-size: 12px;
161
+ font-variant-numeric: tabular-nums;
162
+ }
163
+
164
+ .mvp_stat--total .mvp_statValue {
165
+ font-weight: 600;
166
+ }
167
+
168
+ .mvp_usageHint {
169
+ margin-top: 8px;
170
+ font-size: 11px;
171
+ color: var(--dsw-alias-text-tertiary, currentColor);
172
+ }
173
+
174
+ .mvp_usageHint--warn {
175
+ color: var(--dsw-alias-danger, #d0605e);
176
+ }
177
+
178
+ .mvp_checker {
179
+ margin: 0 0 12px;
180
+ padding: 10px 12px;
181
+ border: 1px solid var(--dsw-alias-border, transparent);
182
+ border-radius: 10px;
183
+ }
184
+
185
+ .mvp_checkerHead {
186
+ display: flex;
187
+ width: 100%;
188
+ align-items: center;
189
+ gap: 10px;
190
+ padding: 0;
191
+ border: 0;
192
+ background: transparent;
193
+ color: inherit;
194
+ text-align: left;
195
+ cursor: pointer;
196
+ }
197
+
198
+ .mvp_checkerTitle {
199
+ font-weight: 600;
200
+ font-size: 12px;
201
+ }
202
+
203
+ .mvp_checkerSummary {
204
+ margin-left: auto;
205
+ font-size: 11px;
206
+ color: var(--dsw-alias-text-tertiary, currentColor);
207
+ }
208
+
209
+ .mvp_checker--closed.mvp_checker {
210
+ padding: 8px 12px;
211
+ }
212
+
213
+ .mvp_checkerBody {
214
+ margin-top: 10px;
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: 10px;
218
+ }
219
+
220
+ .mvp_checkerHint {
221
+ font-size: 11px;
222
+ color: var(--dsw-alias-text-tertiary, currentColor);
223
+ }
224
+
225
+ .mvp_checkerForm {
226
+ display: grid;
227
+ grid-template-columns: 1fr 1fr;
228
+ gap: 10px;
229
+ max-width: 560px;
230
+ }
231
+
232
+ .mvp_checkerActions {
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 10px;
236
+ }
237
+
238
+ .mvp_checkerSwitchText {
239
+ font-size: 12px;
240
+ }
241
+
242
+ .mvp_checkerProgress {
243
+ font-size: 11px;
244
+ font-variant-numeric: tabular-nums;
245
+ color: var(--dsw-alias-text-secondary, currentColor);
246
+ }
247
+
248
+ `